链表三——快慢指针的使用
本篇主要介绍快慢指针。
快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2次,慢指针每次向前移动1次。
一、快慢指针的经典应用:判断一个链表是否存在环
思路:
1、使用快慢指针,慢指针每次走一步,快指针每次走两步。
2、快指针有两种状态:
当链表无环时,快指针走到末尾,这时退出循环;
当链表有环时,快指针就一直走,直到慢指针快指针追上慢指针时,退出循环。
注意:链表的长度分为奇数和偶数,所以快指针没走一步都要判断是否到达末尾(无环时)。
代码如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 typedef struct tag 7 { 8 int Nnum_; 9 struct tag *Nnext_; 10 }Node, *pNode; 11 12 13 void link_print(pNode phead, int size) 14 { 15 int i= 0; 16 while(phead != NULL) 17 { 18 printf("%4d", phead->Nnum_); 19 phead = phead->Nnext_; 20 if( ++i > size) 21 break; 22 } 23 printf("\n"); 24 } 25 //尾插法 26 void link_init_tail(pNode *phead, int size) //传的是地址 27 { 28 pNode pNew = NULL; 29 pNode pTail = NULL; 30 31 while( size > 0) 32 { 33 //申请内存 34 pNew = (pNode)malloc(sizeof(Node)); //注意这里为何不用pNode而用Node,因为sizeof(pNode) = 4 35 //赋值 36 pNew->Nnum_ = rand()%1000; 37 //插入链表 38 if(*phead == NULL) //链表为空时 39 { 40 *phead = pNew;//连接新的结点 41 pTail = pNew; 42 } 43 else//不为空 44 { 45 pTail->Nnext_ = pNew ; //连接新的结点 46 pTail = pNew; //改名字 47 } 48 size --; 49 } 50 /* 51 //生成一个带环的链表 52 pNode pCur = *phead; 53 int i =0; 54 while( i < 3) //attention 55 { 56 pCur = pCur->Nnext_; 57 i++; 58 } 59 pTail->Nnext_ = pCur; //生成一个环 60 */ 61 } 62 63 int isloop( pNode phead) 64 { 65 pNode pFast = phead; //快指针 66 pNode pSlow = phead; //慢指针 67 68 while( pFast->Nnext_ != NULL) 69 { 70 pSlow = pSlow->Nnext_; 71 pFast = pFast->Nnext_; 72 pFast = pFast->Nnext_; 73 if(pFast == NULL)//没有这句的话,若length为偶数,则会Segmentation fault (core dumped) 74 return 0; 75 76 if( pSlow == pFast) //有环存在 77 return 1; 78 } 79 80 if( pFast == NULL) //无环 81 return 0; 82 } 83 84 int main(int argc, char const *argv[]) 85 { 86 srand(time(NULL)); 87 pNode phead = NULL; 88 link_init_tail(&phead, 10); //10为初始链表的长度 89 //从init中可以看出,若真正打印出的数字的长度为15(> 链表的初始长度),则说名存在环 90 link_print(phead,15); 91 92 printf(" ret:%d\n", isloop(phead)); 93 94 return 0; 95 }
二、在有序链表中寻找中位数(快慢指针)
思路:同 一
代码如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 typedef struct tag 7 { 8 int Nnum_; 9 struct tag *Nnext_; 10 }Node, *pNode; 11 12 13 void link_print(pNode phead) 14 { 15 while(phead != NULL) 16 { 17 printf("%4d", phead->Nnum_); 18 phead = phead->Nnext_; 19 } 20 printf("\n"); 21 } 22 23 void link_insert_by_sort(pNode *phead, int size) 24 { 25 pNode pNew = NULL; 26 pNode pCur, pPre; 27 28 while( size > 0 ) 29 { 30 pNew = (pNode)malloc(sizeof(Node)); 31 pNew->Nnum_ = rand()%1000; 32 33 pPre = NULL; 34 pCur = *phead; 35 while( pCur != NULL) 36 { 37 if( pNew->Nnum_ > pCur->Nnum_) //按照从小到大排序 38 { 39 pPre = pCur; 40 pCur = pCur->Nnext_; 41 } 42 else //找到位置 43 break; 44 } 45 //找到的位置在头部 46 if( pPre == NULL) 47 { 48 pNew->Nnext_ = *phead; 49 *phead = pNew; 50 }else//位置在中间或者尾部 51 { 52 pPre->Nnext_ = pNew; 53 pNew->Nnext_ = pCur; 54 } 55 size --; 56 } 57 } 58 59 int find_mid_pos( pNode phead) 60 { 61 pNode pFast = phead; 62 pNode pSlow = phead; 63 64 while(pSlow->Nnext_ != NULL) 65 { 66 pSlow = pSlow->Nnext_; 67 pFast = pFast->Nnext_; 68 pFast = pFast->Nnext_; //考虑链表长度为奇数偶数情况 69 if(pFast == NULL) //偶数时,返回右边值 70 return pSlow->Nnum_ ; 71 } 72 return pSlow->Nnum_ ;//奇数 73 } 74 75 int main(int argc, char const *argv[]) 76 { 77 srand(time(NULL)); 78 pNode phead = NULL; 79 link_insert_by_sort(&phead, 15); 80 link_print(phead); 81 82 printf("ret:%4d\n", find_mid_pos(phead)); 83 return 0; 84 }
三:输入一个链表,输出该链表倒数第K个元素值(快慢指针)
思路:
1、快指针先走K-1个结点,如果连K个结点都没走完,则说明原链表长度不够;
2、在快指针到达末尾之前:慢指针、快指针每次都走一步。
3、当快指针走到末尾时,此时,慢指针所指的结点就是倒数第K个结点。
代码如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 typedef struct tag 7 { 8 int Nnum_; 9 struct tag *Nnext_; 10 }Node, *pNode; 11 12 13 void link_print(pNode phead) 14 { 15 while(phead != NULL) 16 { 17 printf("%4d", phead->Nnum_); 18 phead = phead->Nnext_; 19 } 20 printf("\n"); 21 } 22 23 //头插法 24 void link_init_head(pNode *phead, int size) 25 { 26 pNode pNew = NULL; 27 28 while( size > 0) 29 { 30 //申请内存 31 pNew = (pNode)malloc(sizeof(Node)); //注意这里为何不用pNode而用Node,因为sizeof(pNode) = 4 32 //赋值 33 pNew->Nnum_ = rand()%1000; 34 //插入链表 35 if(*phead == NULL) 36 { 37 *phead = pNew;//连接新的结点; 38 } 39 else 40 { 41 pNew->Nnext_ = *phead;//将pNew的next域置为phead 42 *phead = pNew; 43 } 44 size --; 45 } 46 } 47 48 int backwards_K(pNode phead, int k) 49 { 50 pNode pFast = phead; 51 pNode pSlow = phead; 52 53 int i =0; 54 for ( ; i < k-1; ++i) //倒数第2个,pFast走1 步即可 55 { 56 pFast = pFast->Nnext_; 57 if (pFast == NULL) 58 { 59 printf("the link's length is not enough\n"); 60 return -1; 61 } 62 } 63 64 while( pFast->Nnext_ != NULL) 65 { 66 pSlow = pSlow->Nnext_; 67 pFast = pFast->Nnext_; 68 } 69 return pSlow->Nnum_; 70 } 71 72 int main(int argc, char const *argv[]) 73 { 74 srand(time(NULL)); 75 pNode phead = NULL; 76 link_init_head(&phead, 15); 77 link_print(phead); 78 79 printf("ret:%4d\n", backwards_K(phead, 5)); 80 81 printf("ret:%4d\n", backwards_K(phead, 18)); 82 return 0; 83 }
四:寻找循环链表的入口
题目:假设一个链表存在环,那么怎么寻找环的入口呢?
分析:设链表长度为 L, 起始点到环入口长度为 a,环长度为 r, L=a + r(最好自己画图演示一遍)
在快指针进入环到慢指针进入环前的这段时间,若环的长度较短,也许快指针已经走了好几圈了。然后慢指针进入环,设慢指针和快指针在环内相遇时,慢指针在环内走了 X步,走的总步数(包括环内与环外)为 S 步, 显然 S = X + a,那么快指针走了多少步呢?
快指针在环内已经走了 n圈加 X步,即 nr + X步,其中n至少为 1, 而走的总步数为 nr+X+a步。
由于快指针走的总步数为慢指针的2倍, 因此nr+X+a =(X + a)*2;
由上式可得: a+X = nr, 即 a = nr - X = (n-1)r + r -X;
上式的含义:环入口距离起点的距离(等于a)和相遇点距离环入口点的距离(等于 r -X)相差 (n-1)* r;
所以,让慢指针回到起点,快指针从相遇点开始继续走,步长都为1,当两者相遇时,即为环入口。此时慢指针共走了 a 步,而快指针也走了 a 步(a = (n-1)r + r -X)。代码如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 typedef struct tag 7 { 8 int Nnum_; 9 struct tag *Nnext_; 10 }Node, *pNode; 11 12 13 void link_print(pNode phead, int size) 14 { 15 int i= 0; 16 while(phead != NULL) 17 { 18 printf("%4d", phead->Nnum_); 19 phead = phead->Nnext_; 20 if( ++i > size) 21 break; 22 } 23 printf("\n"); 24 } 25 //尾插法 26 void link_init_tail(pNode *phead, int size) //传的是地址 27 { 28 pNode pNew = NULL; 29 pNode pTail = NULL; 30 31 while( size > 0) 32 { 33 //申请内存 34 pNew = (pNode)malloc(sizeof(Node)); //注意这里为何不用pNode而用Node,因为sizeof(pNode) = 4 35 //赋值 36 pNew->Nnum_ = rand()%1000; 37 //插入链表 38 if(*phead == NULL) //链表为空时 39 { 40 *phead = pNew;//连接新的结点 41 pTail = pNew; 42 } 43 else//不为空 44 { 45 pTail->Nnext_ = pNew ; //连接新的结点 46 pTail = pNew; //改名字 47 } 48 size --; 49 } 50 //生成一个带环的链表 51 pNode pCur = *phead; 52 int i =0; 53 while( i < 8) //attention 54 { 55 pCur = pCur->Nnext_; 56 i++; 57 } 58 pTail->Nnext_ = pCur; //生成一个环 59 } 60 61 pNode find_entrance_of_loop(pNode phead) 62 { 63 pNode pFast = phead; //快指针 64 pNode pSlow = phead; //慢指针 65 66 while( pFast->Nnext_ != NULL) //找环相遇点 67 { 68 pSlow = pSlow->Nnext_; 69 pFast = pFast->Nnext_; 70 pFast = pFast->Nnext_; 71 if(pFast == NULL)//没有这句,若length为偶数,则会Segmentation fault (core dumped) 72 { 73 printf("there is no loop\n"); 74 return NULL; 75 } 76 if( pSlow == pFast) //有环存在 77 break; 78 } 79 if( pFast == NULL) //无环 80 { 81 printf("there is no loop\n"); 82 return NULL; 83 } 84 //有环 85 pSlow = phead; 86 while( pSlow != pFast) //在环入口位置相遇 87 { 88 pSlow = pSlow->Nnext_; 89 pFast = pFast->Nnext_; 90 } 91 return pSlow; 92 } 93 94 int main(int argc, char const *argv[]) 95 { 96 srand(time(NULL)); 97 pNode phead = NULL; 98 link_init_tail(&phead, 25); //10为初始链表的长度 99 //从init中可以看出,若真正打印出的数字的长度为15(> 链表的初始长度),则说明存在环 100 printf("Before:\n"); 101 link_print(phead,40); 102 103 pNode pMeetPos = find_entrance_of_loop(phead); 104 printf("After:\n"); 105 link_print(pMeetPos, 5); 106 return 0; 107 }