[算法]判断一个链表是否有环及环开始的位置

来源:互联网 发布:华为平板有windows系统 编辑:程序博客网 时间:2024/05/29 18:59

这是一道很常见的面试问题,,只用两个变量通过O(n)的时间复杂度就可以解决。
Floyd cycle detection算法,也叫做tortoise and hare算法,龟兔算法吧。
http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare
以下内容为对思路重新梳理了一下,参考了stackoverflow论坛的讨论
http://stackoverflow.com/questions/2936213/explain-how-finding-cycle-start-node-in-cycle-linked-list-work
http://stackoverflow.com/questions/494830/how-to-determine-if-a-linked-list-has-a-cycle-using-only-two-memory-locations。
和thestoryofsnow的博客
http://blog.csdn.net/thestoryofsnow/article/details/6822576
代码来自thefutureisour
http://blog.csdn.net/thefutureisour/article/details/8174313

问题:如何检测一个链表是否有环,如果有,那么如何确定环的起点.

龟兔解法的基本思想可以用我们跑步的例子来解释,如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。

基于上面的想法,Floyd用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行,从后面的讨论我们可以看出这一点。ps:考虑一个链表A有1000000个结点的环,链表B是有1000000个的非环的结点,然后再加一个只有3个结点的小环,如果兔子跑的更快点,即每次前进多于2步,那么能更快的检测链表A,但链表B就很慢,因为要一直绕圈等乌龟,所以选择2步是一个tradeoff)。如果两者在链表头以外的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾,那么说明没环。

证明如下:

这样做的道理我用下图解释这里写图片描述
假设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i

1) i = m + p * n + k
2) 2i = m + q * n + ki

其中,p和q分别为慢指针和快指针在第一次相遇时转过的圈数。

2 ( m + p * n + k ) = m + q * n + k
=> 2m + 2pn + 2k = m + nq + k
=> m + k = ( q - 2p ) n

只要有一组p,q,k满足这个式子,假设就成立了。
我们假设

p = 0
q = m
k = m n - m

那么我们证明如下

m + k = ( q - 2p ) n
=> m + mn - m = ( m - 2*0) n
=> mn = mn.

这时,i为

i = m + p n + k
=> m + 0 * n + mn - m = mn.
假设成立。

环的检测

环的检测是Floyd解法的第二部分。
一旦乌龟和兔子相遇,将慢指针移到链表起点,快指针还在他们相遇的地方,即离环的起点距离k的地方。然后慢指针和快指针同时移动,每次移动一步,那么两者再次相遇的地方就是环的起点。

证明如下:
让乌龟和兔子都同时移动m+k步,乌龟到了他们最初遇见的地方(远离环起点k的位置)
之前我们得到 m + k = (q - 2p) n
即兔子走了q-2p圈,也到了和乌龟同样的位置
现在我们不让乌龟走 m+k 步,让乌龟只走 m 步,兔子也后退k步,即到了环的起点,这是他们第一次相遇的地方。
当他们相遇时,乌龟走的步数就是环的地点在的位置。

求环的长度的问题,第一次相遇后,再次相遇时,走的距离就是环的长度。

#include <stdio.h>  typedef struct Node  {      int val;      Node *next;  }Node,*pNode;  //判断是否有环  bool isLoop(pNode pHead)  {      pNode fast = pHead;      pNode slow = pHead;      //如果无环,则fast先走到终点      //当链表长度为奇数时,fast->Next为空      //当链表长度为偶数时,fast为空      while( fast != NULL && fast->next != NULL)      {          fast = fast->next->next;          slow = slow->next;          //如果有环,则fast会超过slow一圈          if(fast == slow)          {              break;          }      }      if(fast == NULL || fast->next == NULL  )          return false;      else          return true;  }  //计算环的长度  int loopLength(pNode pHead)  {      if(isLoop(pHead) == false)          return 0;      pNode fast = pHead;      pNode slow = pHead;      int length = 0;      bool begin = false;      bool agian = false;      while( fast != NULL && fast->next != NULL)      {          fast = fast->next->next;          slow = slow->next;          //超两圈后停止计数,挑出循环          if(fast == slow && agian == true)              break;          //超一圈后开始计数          if(fast == slow && agian == false)          {                         begin = true;              agian = true;          }          //计数          if(begin == true)              ++length;      }      return length;  }  //求出环的入口点  Node* findLoopEntrance(pNode pHead)  {      pNode fast = pHead;      pNode slow = pHead;      while( fast != NULL && fast->next != NULL)      {          fast = fast->next->next;          slow = slow->next;          //如果有环,则fast会超过slow一圈          if(fast == slow)          {              break;          }      }      if(fast == NULL || fast->next == NULL)          return NULL;      slow = pHead;      while(slow != fast)      {          slow = slow->next;          fast = fast->next;      }      return slow;  }  

参考资料:

0 0
原创粉丝点击