链表三——快慢指针的使用

本篇主要介绍快慢指针。

快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动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 }
View Code

二、在有序链表中寻找中位数(快慢指针)

思路:同 一

代码如下:

 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 }
View Code

三:输入一个链表,输出该链表倒数第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 }
View Code

四:寻找循环链表的入口

题目:假设一个链表存在环,那么怎么寻找环的入口呢?

分析:设链表长度为 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 }
View Code

 

posted @ 2014-11-11 17:43  Stephen_Hsu  阅读(714)  评论(0编辑  收藏  举报