Floyd判圈算法
Floyd判圈算法(Floyd Cycle Detection Algorithm),又稱龜兔賽跑算法(Tortoise and Hare Algorithm),是一個可以在有限狀態機、迭代函數或者鍊表上判斷是否存在環,求出該環的起點與長度的算法。
如果有限狀態機、迭代函數或者鍊表存在環,那麼一定存在一個起點可以到達某個環的某處(這個起點也可以在某個環上)。
初始狀態下,假設已知某個起點節點為節點S。現設兩個指針t和h,將它們均指向S。
接著,同時讓t和h往前推進,但是二者的速度不同:t每前進1步,h前進2步。只要二者都可以前進而且沒有相遇,就如此保持二者的推進。當h無法前進,即到達某個沒有後繼的節點時,就可以確定從S出發不會遇到環。反之當t與h再次相遇時,就可以確定從S出發一定會進入某個環,設其為環C。
如果確定了存在某個環,就可以求此環的起點與長度。
上述算法剛判斷出存在環C時,顯然t和h位於同一節點,設其為節點M。顯然,僅需令h不動,而t不斷推進,最終又會返回節點M,統計這一次t推進的步數,顯然這就是環C的長度。
為了求出環C的起點,只要令h仍均位於節點M,而令t返回起點節點S,此時h與t之間距為環C長度的整數倍。隨後,同時讓t和h往前推進,且保持二者的速度相同:t每前進1步,h前進1步。持續該過程直至t與h再一次相遇,設此次相遇時位於同一節點P,則節點P即為從節點S出發所到達的環C的第一個節點,即環C的一個起點。
开始时,t与h都位于头节点,每次h先移动2单位,之后判断h是否与t在同一点内,若不在,则轮到t移动一单位;若在同一点,则说明存在一个环。否则如此往复,h一定会移动到链表的尾端,即无法再移动,这种情况便是不存在环的情况。
至于为什么有环会相遇,这点很好理解。想象两个不同速度的人在操场上跑步。即使慢的人领先与快的人,到最后快的人也会追上慢的人。
现在来说明一下,为什么如果有环的话,为什么快指针会在一圈之内追上慢指针。
已知 t(慢指针) 的速度为 1/s h(快指针) 的速度为 2/s 假设在 t 刚入环时,h 在环内的位置距离入环点的距离为 b,设 环长度为 L。 则此时 h 与 t之间的距离(追赶距离)为 L - b 而 h 与 t 的相对速度为:(2-1)/s,也就是每一个周期,h与t的距离都会缩短一个单位 所以,当 h 与 t 相遇时,他们所想好的时间为:(L-b)/(2-1) = (L-b)/s 由于t的速度为1/s 所以,t 与 h 相遇时,t所走的距离为(L-b) 上式中,仅当 b=0 时 t 需要走一圈之后才能与h相遇,但 b=0 这条件是不存在的 若 b=0 这存在,这说明,t刚好追赶上h时是在入环口。 但这很明显是不可能的,起始时h先手走两单位,之后才到t移动一单位。因此在不存在环的情况下, t 永远追及不到 h(这也是判断是否有环的思路) 所以,L-b 一定小于 L 即h在环内可以在一圈之内追及上t
/** *求环内交点 * @param head 头节点 * @return 如果存在环,则返回环内快慢指针相交的节点;若无则返回null */ public static ListNode FindIntersection(ListNode head) { ListNode h = head, t = head; while (h != null ) { if (h.next != null) { h = h.next; t = t.next; } else { return null; } if (h.next != null) { h = h.next; //每次h跑完两步就要检查一下是否追上了t,这里可能会有人有疑问,为什么上面h刚跑完之后不检查是否追上了t //这是因为,后面需要根据这个点来求入环点,而推导公式的基础便是每次h行动2单位都是不可分割的。 if (t == h) { return t; } } } return null; }
如上图,设环外长度为a,h与t相交于环内的A点,其距离入环口的距离为b,一圈长度为 C
由上面的现在来说明一下,为什么如果有环的话,为什么快指针会在一圈之内追上慢指针。
可知,相遇时,t在环内走过的距离不会超过一圈
(a + C*0 +b ) - (a + C*n + b) = C*n
所以可见 Lt 为环长的整数倍。
现在让 t 指针回到头节点,h 指针保持在 A 点,然后 两指针同步的一起移动 a 单位(其中t指针的作用是贡献 a 长度,使其与环内的已有长度 b 的 h 构成刚好一个环的长度)到达环入口。由于一开始 h 在环内距离入环点的距离为 b ,所以当 t 移动了 a 单位时,h在环内的长度为 b + a = Lt 所以此时 h 也刚好到达环入口(想一下,从一个地方进入操场跑一圈当然是会回到入口点的)。
所以当 t 与 h相交时便是环的入口。
public ListNode detectCycle(ListNode head) { if (head == null) { return null; } ListNode intersListNode = FindIntersection(head); if (intersListNode != null) { while (head != intersListNode) { head = head.next; intersListNode = intersListNode.next; } return head; } else { return null; } }
当找到环内交点时,令其中指针一个不动,而另一个指针每次移动一单位。最后再次相遇时所走过的距离便是环的长度。
/** * * @param intersListNode 循环圈内 快慢指针的交点 * @return 一个圈的长度 */ public static int LoopLength(ListNode head ,ListNode intersListNode) { int cnt = 0; //无环的情况 if (intersListNode == null) return cnt; //无环外长的情况 if (head == intersListNode) { cnt++; head = head.next; while (head != intersListNode && cnt++>0) head = head.next; } //有环外长的情况 else { ListNode t = intersListNode.next; cnt++; while (t != intersListNode && cnt++>0) t = t.next; } return cnt; }
import java.util.ArrayList; import java.util.HashMap; // Definition for singly-linked list. class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } } public class Solution { public ListNode detectCycle(ListNode head) { if (head == null) { return null; } ListNode intersListNode = FindIntersection(head); if (intersListNode != null) { while (head != intersListNode) { head = head.next; intersListNode = intersListNode.next; } return head; } else { return null; } } /** * * @param head 头节点 * @return 如果存在环,则返回环内快慢指针相交的节点;若无则返回null */ public static ListNode FindIntersection(ListNode head) { ListNode h = head, t = head; while (h != null ) { if (h.next != null) { h = h.next; t = t.next; } else { return null; } if (h.next != null) { h = h.next; //每次h跑完两步就要检查一下是否追上了t,这里可能会有人有疑问,为什么上面h刚跑完之后不检查是否追上了t //这是因为,后面需要根据这个点来求入环点,而推导公式的基础便是每次h行动2单位都是不可分割的。 if (t == h) { return t; } } } return null; } /** * * @param intersListNode 循环圈内 快慢指针的交点 * @return 一个圈的长度 */ public static int LoopLength(ListNode head ,ListNode intersListNode) { int cnt = 0; //无环的情况 if (intersListNode == null) return cnt; //无环外长的情况 if (head == intersListNode) { cnt++; head = head.next; while (head != intersListNode && cnt++>0) head = head.next; } //有环外长的情况 else { ListNode t = intersListNode.next; cnt++; while (t != intersListNode && cnt++>0) t = t.next; } return cnt; } /** * * @param enm 链表的前后驱节点的关系 * @param head 头节点 * @return 尾结点 */ public static ListNode init(int[][] enm, ListNode head) { HashMap<Integer, ListNode> map = new HashMap<Integer, ListNode>(); ListNode t = head; map.put(0, head); for (int i = 0; i < enm.length; i++) { ListNode node; if (map.containsKey(enm[i][1])) { node = map.get(enm[i][1]); } else { node = new ListNode(enm[i][1]); map.put(enm[i][1], node); } t.next = node; t = node; } return t; } public static void main(String[] st) { ListNode head = new ListNode(0); Solution ss = new Solution(); // int[][] enm = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 3 } }; int[][] enm = { { 0, 1 }, { 1, 0}}; init(enm, head); System.out.println(LoopLength(head, FindIntersection(head))); System.out.println(ss.detectCycle(head).val); } }