[算法]判断一个链表是否有环及环开始的位置
来源:互联网 发布:华为平板有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; }
参考资料:
- [算法]判断一个链表是否有环及环开始的位置
- 算法7— 判断一个单链表是否有环,如果有,找出环的起始位置
- 判断一个链表是否有环,如果有环返回环开始的结点指针
- 判断一个链表是否有环以及环的位置入口
- [笔试题]判断链表是否有环及环的位置问题
- [算法]判断一个链表是否有环
- 判断单向链表中是否有环并确定开始位置
- 算法:判断一个链表中是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 判断一个链表是否有环
- 算法-判断链表是否有环
- 判断一个单向链表是否有环路的算法
- 判断一个单链表是否有环,如果有,找出环的起始位置 [No. 36]
- Android Volley完全解析(三),定制自己的Request
- OpenGL--摄像机漫游
- 【机器视觉】【知乎】机器视觉与计算机视觉的区别?
- Linux中ipcs和ipcrm命令
- if let用法简述
- [算法]判断一个链表是否有环及环开始的位置
- 【C#】类型转换
- 系统原生设置总结的各种接口方法
- sql之left join、right join、inner join的区别
- Column count doesn't match value count at row 1
- ArrayAdapter作为ListView适配器,ListView作为LinearLayout子控件时,ListView的item不可点击
- c语言:Atexit
- IDEA配置tomcat
- 插入区间