链表常见操作
2010-05-12 22:00
477 查看
面试常会考到链表和C库函数的操作,特地整理下了链表的常用函数,以供参考:
一.链表
链表定义如下:
为了程序的可读性,LinkList表示链表头节点指针,用于表示一个链表,pNode表示节点指针
另外我们创建的链表是带头结点的链表,即单链表的首元结点(存放第一个数据元素的结点)之前附设一个头结点(数据域什么都不放),称之为带头结点的单链表,反之就是不带头结点的单链表。
1.创建链表
(1)顺序创建链表
p是临时节点指针,指向最后一个节点.增加一个节点时实际上就是在链表尾部插入一个新节点.
(2)逆序创建链表
逆序创建链表时就是将新的节点(pNew)插入到Head节点之后.
2.获取某个指定结点的值
3.插入元素
4.删除元素
5.反序链表
最简单就是前插法,将头结点摘下来,然后采用前插法依次插入,例如:
初始: head -> 1 -> 2 -> 3
第一步: head
第二步: head -> 1
第三步: head -> 2 -> 1
第四步: head -> 3 -> 2 -> 1
6.找出链表中间节点
采用两个节点指针,一个一次移动2个节点,一个一次移动一个节点,这样只需偏历一次链表就可以找到中间节点了.
7.判断一个单链表是否有环
这道题是《C专家编程》中的题了.这里采用的算法是一个指针一次走一步,另一个一次走两步,如果链表有环,那么这两个指针一定会相遇.
8.快速排序
二.C库函数
1. itoa
数字转换为字符串
2.atoi
字符串转换成整型数
上面的这个比较简单,下面看看微软的和glibc里的实现
微软:
glibc:
3.strcpy
4.memcpy
5.memmove
memmove和memcpy的区别是考虑的地址重叠情况
6.memset
7.strcmp
一.链表
链表定义如下:
为了程序的可读性,LinkList表示链表头节点指针,用于表示一个链表,pNode表示节点指针
#include<stdio.h> #include<stdlib.h> #include<conio.h> typedef struct Node { int data; struct Node *next; }Node,*LinkList,*pNode; //为了简单起见,我们讲链表值保存到一个数组中,这样链表操作后可以显而易见的看到运行结果是否正确. int LinkListData[10] = {0,1,2,3,4,5,6,7,8,9};
另外我们创建的链表是带头结点的链表,即单链表的首元结点(存放第一个数据元素的结点)之前附设一个头结点(数据域什么都不放),称之为带头结点的单链表,反之就是不带头结点的单链表。
1.创建链表
(1)顺序创建链表
p是临时节点指针,指向最后一个节点.增加一个节点时实际上就是在链表尾部插入一个新节点.
LinkList CreateListSequence(int num) { pNode p,pHead,pEnd; int i; if(!(p = pHead = (LinkList)malloc(sizeof(Node)))) { return NULL; } p->next = pHead->next = NULL; for(i=0;i<num;i++) { if(pEnd = ((LinkList)malloc(sizeof(Node)))) { pEnd->data = LinkListData[i]; pEnd->next = p->next; p->next = pEnd; p = pEnd; } } return pHead; }
(2)逆序创建链表
逆序创建链表时就是将新的节点(pNew)插入到Head节点之后.
LinkList CreateListInverse(int num) { LinkList pHead,pNew; int i; if(!(pHead=(LinkList)malloc(sizeof(Node)))) { return NULL; } pHead->next = NULL; for(i=0;i<num;i++) { if(pNew=(LinkList)malloc(sizeof(Node))) { pNew->data = LinkListData[i]; pNew->next = pHead->next; pHead->next = pNew; } } return pHead; }
2.获取某个指定结点的值
int GetElement(LinkList list,int location) { int i = 0; pNode point = list->next; while(point && i<location) { point = point->next; i++; } if(!point || i > location) return 0; return point->data; }
3.插入元素
int ListInsert(LinkList list,int location,int arg) { pNode point=list->next,node; int i=1; while(i<location) { point = point->next; i++; } if(!point || (i > location)) { printf("/nInsert operatoin failed"); return 0; } if(!(node =(LinkList)malloc(sizeof(Node)))) return 0; node->data = arg; node->next = point->next; point->next = node; return 1; }
4.删除元素
void ListDelete(LinkList list,int location) { LinkList point = list->next; int i = 1; while(i < location) { point = point->next; list = list->next; i++; } if(!point || (i > location)) { printf("/nDelete operation failed!/n"); return; } list->next = point->next; free(point); }
5.反序链表
最简单就是前插法,将头结点摘下来,然后采用前插法依次插入,例如:
初始: head -> 1 -> 2 -> 3
第一步: head
第二步: head -> 1
第三步: head -> 2 -> 1
第四步: head -> 3 -> 2 -> 1
LinkList ReverseLinkList(LinkList pHead) { pNode p = pHead-> next; pHead->next = NULL; while(p){ pNode q=p; p = p-> next; q->next = pHead-> next; pHead->next = q; } return pHead; }
6.找出链表中间节点
采用两个节点指针,一个一次移动2个节点,一个一次移动一个节点,这样只需偏历一次链表就可以找到中间节点了.
Node* find_midlist(LinkList pHead) { pNode p1, p2; if(pHead == NULL || pHead->next == NULL) return pHead; //链表有头结点,如无头结点,则为p1 = p2 = pHead->next p1 = p2 = pHead->next; while (1) { if (p2->next != NULL && p2->next->next != NULL) { p2 = p2->next->next; p1 = p1->next; } else { break; } } return p1; }
7.判断一个单链表是否有环
这道题是《C专家编程》中的题了.这里采用的算法是一个指针一次走一步,另一个一次走两步,如果链表有环,那么这两个指针一定会相遇.
int is_looplist (LinkList pHead) { pNode p1, p2; p1 = p2 = pHead; if (pHead == NULL || pHead->next == NULL) return 0; while (p2->next != NULL && p2->next->next != NULL) { p1 = p1->next; p2 = p2->next->next; if (p1 == p2) return 1; } return 0; }
8.快速排序
void BubbleSort(LinkList list) { int temp; pNode p1,p2; p1 = list->next; p2 = p1->next; while(p1->next) { if(p1->data > p2->data) { temp = p1->data; p1->data = p2->data; p2->data = temp; } p2 = p2->next; if(!p2) { p1 = p1->next; p2 = p1->next; } } }
二.C库函数
1. itoa
数字转换为字符串
static void itoa(int n, char s[]) { int i = 0; //获得一个数的绝对值 unsigned int val = (unsigned int)((n < 0) ? -n : n); //从后向前提取数字 do{ s[i++] = (val % 10) + '0'; }while(val/=10); //如果数字为负,添加-号 if(n < 0) s[i] = '-'; s[i+1] = '/0'; //颠倒字符串 for(n=0;n<i;n++,i--) { char swap = s ; s = s[i]; s[i] = swap; } }
2.atoi
字符串转换成整型数
int atoi(char s[]) { int sign=1,num=0; if(*s=='-') { sign=-1; s++; } else if(*s=='+') { sign=1; s++; } while((*s)!='/0') { num=num*10+(*s-'0'); s++; } return num*sign; }
上面的这个比较简单,下面看看微软的和glibc里的实现
微软:
long atol(const char *nptr) { int c; /* current char */ long total; /* current total */ int sign; /* if '-', then negative, otherwise positive */ /* skip whitespace */ while ( isspace((int)(unsigned char)*nptr) ) ++nptr; c = (int)(unsigned char)*nptr++; sign = c; /* save sign indication */ if (c == '-' || c == '+') c = (int)(unsigned char)*nptr++; /* skip sign */ total = 0; while (isdigit(c)) { total = 10 * total + (c - '0'); /* accumulate digit */ c = (int)(unsigned char)*nptr++; /* get next char */ } if (sign == '-') return -total; else return total; /* return result, negated if necessary */ } int atoi(const char *nptr) { return (int)atol(nptr); }
glibc:
#define LONG_MAX 2147483647L #define LONG_MIN (-2147483647L-1L) long int _strtol_internal (const char *nptr, char **endptr, int base, int group) { unsigned long int result = 0; long int sign = 1; while (*nptr == ' ' || *nptr == '/t') ++nptr; if (*nptr == '-') { sign = -1; ++nptr; } else if (*nptr == '+') ++nptr; if (*nptr < '0' || *nptr > '9') { if (endptr != NULL) *endptr = (char *) nptr; return 0L; } assert (base == 0); base = 10; if (*nptr == '0') { if (nptr[1] == 'x' || nptr[1] == 'X') { base = 16; nptr += 2; } else base = 8; } while (*nptr >= '0' && *nptr <= '9') { unsigned long int digval = *nptr - '0'; if (result > LONG_MAX / 10 || (sign > 0 ? result == LONG_MAX / 10 && digval > LONG_MAX % 10 : (result == ((unsigned long int) LONG_MAX + 1) / 10 && digval > ((unsigned long int) LONG_MAX + 1) % 10))) { errno = ERANGE; return sign > 0 ? LONG_MAX : LONG_MIN; } result *= base; result += digval; ++nptr; } return (long int) result * sign; }
3.strcpy
char* strcpy(char* dst,const char* src) { char* strDst = dst; assert(src != NULL && dst != NULL);//拷贝空串被认为是没有意义的,使用assert检查 while ((*dst++ = *src++) != '/0'); return strDst; }
4.memcpy
void* memcpy(void* dst,const void* src,size_t count) { char* pbTo = (char*)dst; char* pbFrom = (char*)src; assert(dst != NULL && src != NULL); assert(pbTo >= pbFrom+count || pbFrom >= pbTo + count);//防止内存重叠(overlap) while (count-- > 0) { *pbTo++ = *pbFrom++; } return dst; }
5.memmove
memmove和memcpy的区别是考虑的地址重叠情况
void* memmove(void* dst,const void* src,size_t count) { char* pbTo = (char*)dst; char* pbFrom = (char*)src; assert(dst != NULL && src != NULL); if (dst <= src || pbTo >= pbFrom + count)//没有overlap的情况,直接拷贝 { while (count-- > 0) { *pbTo++ = *pbFrom++; } } else { pbTo = pbTo + count -1;//overlap的情况,从高位地址向低位拷贝 pbFrom = pbFrom + count -1; while (count-- > 0) { *pbTo-- = *pbFrom--; } } return dst; }
6.memset
void* memset(void* buf,int c,size_t count) { char* pvTo = (char*)buf; assert(buf != NULL); while (count-- >0) { *pvTo++ = (char)c; } return buf; }
7.strcmp
int strcmp(const char* src,const char* dst) { int ret = 0; if (src == dst) { return 0; } assert(NULL != src);//期待源字符串不为空 if (dst == NULL) { return -1; } while (!(ret = *(unsigned char*)src - *(unsigned char*)dst) && *dst) { ++src,++dst; } if (ret < 0) { ret = -1; } else if (ret > 0) { ret = 1; } return ret; }