Floyd判圈算法
·
Floyd判圈算法(Floyd Cycle Detection Algorithm),又称龟兔赛跑算法,该算法可以:
- 判断链表是否有环
- 计算环的长度
- 寻找环的起点
原理:可以想象两个人在操场上跑步,A跑得快,B跑得慢。A领先,然后在超过B一圈的地方和B相遇,假设A和B的步伐是匀速的,那么他们相遇的位置一定是起始位置,因为超过的一定是圈的N倍。
判断链表是否有环
【快慢指针】定义两个指针,慢指针slow(每次前进一步),快指针(fast)每次前进两步,这里只要fast比slow前进的快即可,但前进步长太多会增加代码运行时间,所以采用步长2。
- 若无环,fast先走到终点;若有环,最终slow和fast相遇,且fast比slow多走了N圈
- 因此循环的结束条件为要么快的先为null,要么slow==fast
- 初始化slow=head,fast=head.next,是因为如果fast初始化为head,那循环的第一个条件slow!=fast永远都不成立
public boolean isLoop(Node head) {
if (head == null) {
return false;
}
Node slow = head;
Node fast = head.next;
// fast比slow快,判空仅判断fast即可。
// fast != null && fast.next != null因为fast每次走过两个格,不确定此时在最后一个位置还是在空位置,因此都需判断
while (slow != fast && fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow == fast;
}
计算环的长度
当slow和fast相遇时,说明存在环,然后用一指针从相遇点走,直到走回来,记录长度。没有环长度返回0
public static int length(ListNode head) {
if (head == null) {
return 0;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast && fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 确定有环,开始计算长度
if (slow == fast) {
int num = 1;
ListNode temp = slow.next;
while (temp != slow) {
temp = temp.next;
num++;
}
return num;
}
return 0;
}
寻找环的起点
- 若slow和fast从环内同一起点出发,那么再次相遇的位置一定是起点
- 若slow和fast从环内不同起点出发,那么再次相遇的位置会发生偏移
- 若slow和fast从环外同一起点出发,在进入环之前还有一段距离,因此相遇的位置不一定就是起点
解法一【无需知道环的长度】:

假设:环的长度为L,非环部分长度为a,快慢指针相遇的地方距离环的入口位置为b。slow走过的圈数为N(slow),fast走过的圈数为N(fast)
此时:
- slow走过的长度为:a+N(slow) * L+b
- fast走过的长度为:a+N(fast) * L+b
因此此时两者的差值为N(圈)的整数倍。然后将slow挪至最开始的起点,同时fast和slow开始同时移动步长1直至相遇。

- 由于fast移动步长为2,当slow和fast相遇,fast走过的路长是slow的两倍,假设slow走过的总距离为S,那么fast总距离则为2S
- 而fast与slow总距离差为环长L的整倍数,因此S总距离就是环长L的整数倍
- 当slow从起点走到入口,走过的距离为a;fast从相遇位置走距离a,共走过距离为2S+a
- 因此fast从起点开始,走过了2S+a的路程,去掉前面的a,2S又是环长的整数倍,此时即为入口处,且slow和fast相遇。
以上所有的计算都是以slow和fast同一起点出发计算
public static ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
// 相遇后,将slow挪至起点
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
解法二【需要知道环的长度】:代码略
首先知道环的长度为L,非环部分长a。将快慢指针同时指向头结点。快指针首先移动距离L后,慢指针开始移动,其相遇时就在环的入口,此时fast走过的距离为a+L,slow走过的距离为a,因此fast-slow=L,刚好一圈。
更多推荐


所有评论(0)