標籤:cycle 9.png 否則 ret 第一個 slow gets sum 而且
2018-01-13 20:55:56
Floyd判圈演算法(Floyd Cycle Detection Algorithm),又稱龜兔賽跑演算法(Tortoise and Hare Algorithm),是一個可以在有限狀態機器、迭代函數或者鏈表上判斷是否存在環,求出該環的起點與長度的演算法。該演算法據高德納稱由美國科學家羅伯特·弗洛伊德發明,但這一演算法並沒有出現在羅伯特·弗洛伊德公開發表的著作中。
如果有限狀態機器、迭代函數或者鏈表上存在環,那麼在某個環上以不同速度前進的2個指標必定會在某個時刻相遇。同時顯然地,如果從同一個起點(即使這個起點不在某個環上)同時開始以不同速度前進的2個指標最終相遇,那麼可以判定存在一個環,且可以求出2者相遇處所在的環的起點與長度。
一、演算法描述
如果有限狀態機器、迭代函數或者鏈表存在環,那麼一定存在一個起點可以到達某個環的某處(這個起點也可以在某個環上)。
初始狀態下,假設已知某個起點節點為節點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的一個起點。
環入口演算法的證明:
假設慢指標到相遇點的距離為l,則快指標的路程為2l,環的長度為r。
l = x + y;
2l = l + nr;
==> nr = x + y
==> x = nr - y
那麼此時,將相遇點的慢指標調到起始點,快指標行進速度和慢指標保持一致,那麼當慢指標走了x的路程時,快指標走了nr - y,正好兩者在環的入口處相遇。
二、虛擬碼描述
1 t := &S 2 h := &S //令指標t和h均指向起點節點S。 3 repeat 4 t := t->next 5 h := h->next 6 if h is not NULL //要注意這一判斷一般不能省略 7 h := h->next 8 until t = h or h = NULL 9 if h != NULL //如果存在環的話 10 n := 0 11 repeat //求環的長度 12 t := t->next 13 n := n+1 14 until t = h 15 t := &S //求環的一個起點 16 while t != h 17 t := t->next 18 h := h->next 19 P := *t
三、演算法複雜度
時間複雜度:注意到當指標t到達環C的一個起點節點P時(此時指標h顯然在環C上),之後指標t最多僅可能走1圈。若設節點S到P距離為m,環C的長度為n,則時間複雜度為O(m+n),是線性時間的演算法。
空間複雜度:僅需要創立指標t、指標h,儲存環長n、環的一個起點P。空間複雜度為O(1),是常數空間的演算法。
四、應用
對於有限狀態機器與鏈表,可以判斷從某個起點開始是否會返回到訪問過運行過程中的某個狀態和節點。
對於迭代函數,可以判斷其是否存在周期,以及求出其最小正周期。
五、相關演算法
雖然Floyd判圈演算法已經達到了線性時間複雜度和常數空間複雜度,但是Brent判圈演算法將減小時間複雜度的常數係數,平均消耗時間比Floyd判圈演算法少36%。
Bruent演算法描述:
Bruent演算法裡有運動的兔子和靜止的烏龜。這裡的烏龜在兔子行進步數到達step_limit時,會傳送到兔子的位置,同時將兔子的行進步數重設為0,同時提高step_limit為原來的兩倍。
烏龜和兔子都從名單的頂部開始。兔子每迭代一步。如果是和固定的烏龜一樣的位置,那顯然是一個迴圈。如果到達列表的末尾,則沒有迴圈。
為什麼要移動烏龜呢?如果一隻兔子被卡在一個迴圈中,沒有遇到烏龜,它將永遠迴圈,所以需要將烏龜在一定步數後傳送到兔子位置。
為什麼每次要花兩倍的時間?最終,傳送之間的時間長度將比迴路的長度更長,因此當兔子完成一圈時,烏龜將在那裡等待兔子。
Bruent演算法的虛擬碼描述:
1 turtle = top 2 rabbit = top 3 4 steps_taken = 0 5 step_limit = 2 6 7 forever: 8 if rabbit == end: 9 return ‘No Loop Found‘10 rabbit = rabbit.next11 12 steps_taken += 113 14 if rabbit == turtle:15 return ‘Loop found‘16 17 if steps_taken == step_limit:18 steps_taken = 019 step_limit *= 220 // teleport the turtle21 turtle = rabbit
六、相關題目摘錄
public boolean isHappy(int n) { int slow = n; int fast = n; do { slow = calc(slow); fast = calc(fast); fast = calc(fast); }while(slow != fast); if(slow != 1) return false; else return true; } int calc(int n) { int sum = 0; while (n > 0) { int k = n % 10; sum += k*k; n /= 10; } return sum; }
代碼解釋:環中顯然是不可能存在1的,所以如果落入了環中,那麼就不可能算得1,否則兩者在運算到1後相等。
Floyd判圈演算法 Floyd Cycle Detection Algorithm