数据结构和算法,是咱们程序设计最重要的两大元素,能够说,咱们的编程,都是在选择和设计合适的数据结构来存放数据,而后再用合适的算法来处理这些数据。html
在面试中,最常常被说起的就是链表,由于它简单,但又由于须要对指针进行操做,凡是涉及到指针的,都须要咱们具备良好的编程基础才能确保代码没有任何错误。面试
链表是一种动态的数据结构,由于在建立链表时,咱们不须要知道链表的长度,当插入一个结点时,只须要为该结点分配内存,而后调整指针的指向来确保新结点被链接到链表中。因此,它不像数组,内存是一次性分配完毕的,而是每添加一个结点分配一次内存。正是由于这点,因此它没有闲置的内存,比起数组,空间效率更高。算法
下面列举几个面试中常见的单链表实现的面试题:编程
一、不遍历链表删除非尾节点数组
一般咱们作链表节点的删除时,都须要知道目标节点的前驱,让前驱的next指针指向目标节点的next,而后释放目标节点。但当咱们不知道目标节点的前驱时,就不能经过这种方法来实现目标节点的删除,此时就须要用后一个节点覆盖目标节点,而后删除后一个节点的方法来实现目标节点的删除,即将后一个节点的data复制到目标节点中,让目标节点的next指向后一个节点的next,而后释放后一个节点,完成目标节点的删除。数据结构
代码以下:数据结构和算法
void RemoveNodeNotTail(SListNode **ppFirst, SListNode *pos) { SListNode *pDel = pos; SListNode *pNode = pDel->pNext; assert(pos); pos->data = pNode->data; pos->pNext = pNode->pNext; free(pNode); }
二、遍历一次,找到中间节点函数
只遍历一遍要找到中间节点,咱们能够定义一快一慢两个指针pFast和pSlow,让他们同时从链表的头结点开始向后遍历,慢指针一次向后走一步,快指针一次向后走两步,当pFast == NULL时,pSlow所指向的位置即为链表的中间节点。oop
代码以下:spa
SListNode* FindMid(SListNode *pFirst) { SListNode *pSlow = pFirst; SListNode *pFast = pFirst->pNext; assert(pFirst); while(pFast) { pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; } pSlow->pNext = NULL; return pSlow; }
三、遍历一次,找到倒数第 k 个结点(k从1开始)
遍历一次找到倒数第k个节点,原理同遍历一次找到中间节点相同,定义一快一慢两个指针pFast和pSlow,都指向链表的头结点,让pFast先向后遍历k-1步,而后让两个指针同时向后遍历,当pFast == NULL时,pSlow所指向的位置即为倒数第k个节点所在的位置。
代码以下:
SListNode* FindK(SListNode *pFirst, int k) { SListNode *pSlow = pFirst; SListNode *pFast = pFirst; assert(pFirst); while(k) { pFast = pFast->pNext; k--; } while(pFast) { pSlow = pSlow->pNext; pFast = pFast->pNext; } return pSlow; }
四、遍历一次,删除倒数第 k 个结点(k从1开始),不能用替换删除法
上一道题咱们已经能找到倒数第k个节点,但要删除它而且不能用替换法,那么须要咱们在遍历时找到第k-1个节点,即k的前一个节点,而后将第k-1个节点的next指向第k个节点的next,删除第k个节点便可。
代码以下:
void RemoveK(SListNode *pFirst, int k) { SListNode *pSlow = pFirst; SListNode *pFast = pFirst; SListNode *pDel; while(k+1) { pFast = pFast->pNext; k--; } while(pFast) { pSlow = pSlow->pNext; pFast = pFast->pNext; } pDel = pSlow->pNext; pSlow->pNext = pDel->pNext; free(pDel); }
五、约瑟夫环
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n我的(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那我的出列;他的下一我的又从1开始报数,数到m的那我的又出列;依此规律重复下去,直到圆桌周围的人所有出列。一般解决这类问题时咱们把编号从0~n-1,最后结果+1即为原问题的解。
这里咱们以k = 3为例,大体过程如图所示:
以红色箭头处开始计数,数到第三个时删除,以此类推,当只剩下一个元素时中止。
当咱们用链表实现时,首先要将一个单链表构形成一个环,此时咱们只须要遍历一次链表,找到链表的最后一个节点,让最后一个节点的next指向第一个节点便可。构成环后,从第一个节点开始向后遍历,没走三个节点,作一次删除,直至链表只剩下一我的节点时中止,返回最后一个节点。
代码以下:
SListNode * JocephCircle(SListNode *pFirst, int k) { SListNode *pLast = pFirst; SListNode *pNode = pFirst; SListNode *pKill; for(pLast; pLast->pNext != NULL; pLast=pLast->pNext) { ; } pLast->pNext = pFirst; //将单链表构形成环 while(pNode->pNext != pNode) { int i = 0; for(pNode; pNode->pNext != NULL; ) { pNode=pNode->pNext; i++; if(i == k-2) { break; } } pKill = pNode->pNext; pNode->pNext = pKill->pNext; pNode = pNode->pNext; free(pKill); } pNode->pNext = NULL; return pNode; }
六、冒泡排序
冒泡排序(Bubble Sort)是最经典也是最简单的排序算法之一,步骤为:
1)、比较相邻的元素。若是第一个比第二个大,就交换他们两个。
2)、对每一对相邻元素做一样的工做,从开始第一对到结尾的最后一对。这步作完后,最后的元素会是最大的数。
3)、针对全部的元素重复以上的步骤,除了最后一个。
4)、持续每次对愈来愈少的元素重复上面的步骤,直到没有任何一对数字须要比较。
单链表实现冒泡排序代码以下:
void BubbleSort(SListNode *pFirst) { SListNode *pNode = pFirst; SListNode *pCpr = pNode->pNext; SListNode *pCheck ; SListNode *pEnd = (SListNode*)malloc(sizeof(SListNode)); int set; int flag = 1; pEnd = NULL; while(flag) { pNode = pFirst; pCpr = pNode->pNext; pCheck = pNode; while(pCpr != pEnd) { if(pNode->data <= pCpr->data) { pNode = pCpr; pCpr = pNode->pNext; } else { set = pNode->data; pNode->data = pCpr->data; pCpr->data = set; pNode = pCpr; pCpr = pNode->pNext; } } pEnd = pNode; for(pCheck; pCheck->pNext != NULL; pCheck=pCheck->pNext) //检查是否全部元素都不须要交换 { if(pCheck->data > pCheck->pNext->data) { flag = 1; break; } flag = 0; } } }
七、合并两个有序链表
假设有两个有序的链表,先须要将两个链表合并成一个链表,并使合并后的链表依然有序。
这时咱们能够从新定义一个新的链表,而后分别从两个须要合并的链表中拿出第一节点,比较他们的大小,将较小的节点拿出尾插入新链表,而后重复此步骤,直至有一个链表为空,最后将不为空的链表的左右节点尾插入新链表便可。
代码以下:
SListNode* MergeOrderedList(SListNode *p1First, SListNode *p2First) { SListNode *pFirst; SListNode *pNode = NULL; while(p1First && p2First) //当两个链表都不为空时,比较两个链表第一个节点元素的大小 { if(pNode == NULL) { if(p1First->data <= p2First->data) { pNode = p1First; p1First = p1First->pNext; } else { pNode = p2First; p2First = p2First->pNext; } pFirst = pNode; } if((pNode != NULL)&&(p1First->data <= p2First->data)) { pNode->pNext = p1First; p1First = p1First->pNext; pNode = pNode->pNext; } else if((pNode != NULL)&&(p1First->data > p2First->data)) { pNode->pNext = p2First; p2First = p2First->pNext; pNode = pNode->pNext; } } if(p1First) //当第二个链表为空,第一个链表不为空时,将第一个链表的剩余节点尾插入新链表 { for(p1First; p1First != NULL; p1First=p1First->pNext) { pNode->pNext = p1First; pNode = pNode->pNext; } } else //当第一个链表为空,第二个链表不为空时,将地二个链表的剩余节点元素尾插入新链表 { for(p2First; p2First != NULL; p2First=p2First->pNext) { pNode->pNext = p2First; pNode = pNode->pNext; } } return pFirst; }
八、判断链表是否带环;若带环,求环的长度和入口点
要判断一个链表是否带环,只须要定义一快一慢两个指针pFast和pSlow,让他们同时从链表的头结点开始向后遍历,慢指针一次向后走一步,快指针一次向后走两步,若两个指针最终相遇,则链表带环;若快指针遍历到NULL,则代表链表不带环。
要求环的长度,首先要肯定环的入口点:
当pSlow和pFast第一次相遇时,pSlow走过的路程为l + a,pFast走过的路程为l + 2*a + c,又由于咱们知道pFast的速度为pSlow的两倍,所以可得出l = c,此时咱们只须要定义一个pMeet指针从头节点出发开始遍历,pSlow从相遇点出发继续遍历,当两个指针相遇时,所指向的节点即为环的入口节点。
知道了环的入口节点,那么环的长度就十分好求了。用pMeet指针记录环的入口节点,pSlow从入口节点出发向后遍历,当两个指针再次相遇时,pSlow所走过的路径即为环的长度。
代码以下:
void IsLoopSList(SListNode *pFirst) { SListNode *pSlow = pFirst; SListNode *pFast = pFirst->pNext->pNext; SListNode *pEnter = (SListNode*)malloc(sizeof(SListNode)); int flag = 1; int LoopLen = 0; while((pSlow != pFast) && (pFast != NULL)) { if(flag) { pSlow = pSlow->pNext; flag = 0; continue; } pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; } if(pFast != NULL) { SListNode *pMeet = pSlow; SListNode *pNode = pFirst; while(pNode != pMeet) { pNode = pNode->pNext; pMeet = pMeet->pNext; } pEnter->data = pMeet->data; pEnter->pNext = NULL; printf("该链表带环,环的入口点为:> "); PrintSList(pEnter); pFast = pFast->pNext->pNext; while(pSlow != pFast) { if(LoopLen == 0) { pSlow = pSlow->pNext; LoopLen++; continue; } pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; LoopLen++; } printf("环的长度为:> %d\n", LoopLen); return; } printf("该链表不带环\n"); }
九、判断两个链表是否相交;相交则求交点(链表不带环)
若两个不带环的链表相交,则他们的尾节点必相同;若要求交点,则须要比较两个链表的长度,让较长的链表先向后遍历至和较短的链表长度相等,而后两个链表同时向后遍历,并比较节点是否相同,当遇到第一个相同的节点时,则为两个链表的交点。
代码以下:
int SListLen(SListNode *pFirst) //求链表长度 { int len = 0; SListNode *pSlow = pFirst; SListNode *pFast = pFirst->pNext->pNext; SListNode *pEnter = (SListNode*)malloc(sizeof(SListNode)); int flag = 1; pEnter = NULL; while((pSlow != pFast) && (pFast != NULL)) { if(flag) { pSlow = pSlow->pNext; flag = 0; continue; } if(pFast->pNext == NULL) { pFast = NULL; break; } pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; } if(pFast != NULL) { SListNode *pMeet = pSlow; SListNode *pNode = pFirst; while(pNode != pMeet) { pNode = pNode->pNext; pMeet = pMeet->pNext; } pEnter->data = pMeet->data; } while(pFirst != NULL || pFirst != pEnter) { len++; pFirst = pFirst->pNext; } return len; } void IsCross(SListNode *p1First, SListNode *p2First) //判断链表是否相交并求交点 { SListNode *p1Node = p1First; SListNode *p2Node = p2First; SListNode *pCross = (SListNode*)malloc(sizeof(SListNode)); int len1 = SListLen(p1First); int len2 = SListLen(p2First); if(len1 > len2) { int k = len1 - len2; while(k) { p1Node = p1Node->pNext; k--; } } if(len2 > len1) { int k = len2- len1; while(k) { p2Node = p2Node->pNext; k--; } } while(p1Node != p2Node && p1Node != NULL && p2Node != NULL) { p1Node = p1Node->pNext; p2Node = p2Node->pNext; } if(p1Node != NULL && p2Node != NULL) { pCross->data = p1Node->data; pCross->pNext = NULL; printf("两个链表相交,交点为:> "); PrintSList(pCross); return; } printf("两个链表不相交\n"); return; }
十、判断两个链表是否相交,若相交则求交点(链表可能带环)
如有两个链表,则他们的带环状况有如下三种可能:
(1)两个链表都不带环
(2)一个链表带环一个链表不带环
(3)两个链表都带环
若出现(1)状况,则只需调用咱们刚写的不带环链表判断相交的函数便可;若出现(2)状况,则两个链表一定不相交;若出现(3)状况,则较为复杂,咱们在这里重点讨论第(3)种状况下的判断。
当两个链表都带环时,可能有如下三种状况:
当出现①状况时,两个链表不相交。
当出现②状况时,两个链表的交点在环外,那么咱们能够转化为不带环链表判断相交便可。
当出现③状况时,两个链表的交点在环内,那么咱们能够遍历其中一个链表的环,若在环内与另外一个链表环的入口点相交,则两个链表相交,相遇点即为两个链表的交点。
要判断为状况②仍是状况③,只需判断两个链表环的入口点是否相同便可。
代码以下:
int SListLen(SListNode *pFirst) //计算链表节点数 { int len = 0; SListNode *pSlow = pFirst; SListNode *pFast = pFirst->pNext->pNext; SListNode *pEnter = (SListNode*)malloc(sizeof(SListNode)); int flag = 1; pEnter = NULL; while((pSlow != pFast) && (pFast != NULL)) { if(flag) { pSlow = pSlow->pNext; flag = 0; continue; } if(pFast->pNext == NULL) { pFast = NULL; break; } pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; } if(pFast != NULL) { SListNode *pMeet = pSlow; SListNode *pNode = pFirst; while(pNode != pMeet) { pNode = pNode->pNext; pMeet = pMeet->pNext; } pEnter->data = pMeet->data; } while(pFirst != NULL || pFirst != pEnter) { len++; pFirst = pFirst->pNext; } return len; } SListNode* GetEnterSList(SListNode *pFirst) //求一个带环链表的入口节点 { SListNode *pSlow = pFirst; SListNode *pFast = pFirst->pNext->pNext; SListNode *pEnter; SListNode *pMeet; SListNode *pNode; int flag = 1; int LoopLen = 0; while(pSlow != pFast) { if(flag) { pSlow = pSlow->pNext; flag = 0; continue; } pSlow = pSlow->pNext; pFast = pFast->pNext->pNext; } pMeet = pSlow; pNode = pFirst; while(pNode != pMeet) { pNode = pNode->pNext; pMeet = pMeet->pNext; } pEnter = pMeet; return pEnter; } void CrossSListWithCycle(SListNode *p1First, SListNode *p2First) { //遍历两个链表 SListNode *p1Slow = p1First; SListNode *p1Fast = p1First->pNext->pNext; SListNode *p2Slow = p2First; SListNode *p2Fast = p2First->pNext->pNext; SListNode *p1Enter; SListNode *p2Enter; SListNode *pCycle; SListNode *pCross = (SListNode*)malloc(sizeof(SListNode)); int flag1 = 1; int flag2 = 1; while(p1Slow != p1Fast && p1Fast != NULL) { if(flag1) { p1Slow = p1Slow->pNext; flag1 = 0; continue; } p1Slow = p1Slow->pNext; p1Fast = p1Fast->pNext->pNext; } while(p2Slow != p2Fast && p2Fast != NULL) { if(flag2) { p2Slow = p2Slow->pNext; flag2 = 0; continue; } p2Slow = p2Slow->pNext; p2Fast = p2Fast->pNext->pNext; } //1.两个链表都不带环,调用不带环链表相交判断函数 if(p1Fast == NULL && p2Fast == NULL) { IsCross(p1First, p2First); return; } //2.一个带环一个不带环,两个链表不相交 if((p1Slow == p1Fast && p2Fast == NULL)||(p2Slow == p2Fast && p1Fast == NULL)) { printf("两个链表不相交\n"); return; } //3.两个链表都带环 // (1).交点在环外 p1Enter = GetEnterSList(p1First); p2Enter = GetEnterSList(p2First); if(p1Enter == p2Enter) { pCycle = p1Enter->pNext; p1Enter->pNext = NULL; IsCross(p1First, p2First); p1Enter->pNext = pCycle; return; } // (2).交点在环内 if(p1Enter != p2Enter) { pCycle = p1Enter->pNext; while(pCycle != p1Enter) { if(pCycle == p2Enter) { printf("两个链表相交,交点为:>"); pCross->data = pCycle->data; pCross->pNext = NULL; PrintSList(pCross); return; } pCycle = pCycle->pNext; } // (3).不相交 printf("两个链表不相交\n"); } }