1. 程式人生 > >判圈演算法(Flyod、Brent's)

判圈演算法(Flyod、Brent's)

問題

如何檢測一個連結串列是否有環?如果有環,如何確定環的起點以及長度?

Floyd cycle detection(龜兔演算法)

龜兔賽跑

對於賽道來說,如果賽道中有環,那麼速度快的兔子一定會在某個地點追上烏龜,並且兔子所跑的距離減去烏龜所跑的距離,一定是環長度的整數倍。

原理

假設令龜、兔為指標,並且指向起點位置,兔子每次移動兩個節點,烏龜每次移動一個節點。如果兩者在起始節點外相遇,則說明有環。如果兔子在走到連結串列尾部還沒有與烏龜相遇,說明無環。

環長度

通過上述演算法判斷出存在環C時,顯然龜兔位於同一節點M,此時,令兔子保持不動,而烏龜不斷推進,記錄移動距離,等再次相遇時,移動步數即環C長度。

環起點

只要令兔子仍位於相遇節點M, 而令烏龜返回連結串列起始節點S,此時烏龜與兔子之間的距離為環C長度的整數倍。隨後,同時讓烏龜與兔子不斷推進一步,直至相遇同一節點P,則節點P即為從連結串列起始節點所到達的環C的第一個節點,即環C起點。

分析

如圖所示,令起始節點為h, 起始節點到環起點距離為m, 龜兔相遇節點為pp節點與環起點距離為n
當龜兔相遇時
烏龜所跑距離為 S = m + n + aC(C 為環長度),
兔子所跑距離為2S = m + n + bC(因為兔子速度為烏龜2倍)
故得S = (b - a)C = m + n + aC ==> (b - 2a)C = m + n

, 由此可知,m + n 為環C長度整數倍.
那麼令烏龜返回連結串列起始節點,兔子仍在相遇節點P,兩者同時不斷推進直至相遇,相遇節點為環起點。因為烏龜移動距離m, 則兔子也移動距離m, 原本p 節點距離環起點為n, 由於m + n 為環長整數倍,故兔子移動到了環起點,即相遇節點為環起點。
Floyd Circle Detection

int *t = head, *h = head;
// 判圈
do{
    h = h->next;
    if(h != null)
        h = h->next;
    t = t->next;
}while(h != t || h != null);

if(h !=
null) { // 環長度 t = head; cnt = 0; while(h != t) { t = t->next; ++cnt; } // 環起點 t = head; while(h != t) { h = h->next; t = t->next; } p = h; // p為起點 }

演算法複雜度

時間複雜度

如果烏龜走到環起點P時,此時顯然兔子已經在環內某節點,之後兔子最多走一圈就會與烏龜相遇。假設連結串列起始節點到環起點距離為m, 環長度為n, 故時間複雜度為O(m + n)

空間複雜度

演算法僅需要建立指標t, h 環長計數n 以及環起點P. 故空間複雜度為O(1)

Brent’s Cycle Detection(The Teleporting Turtle)

原理

起始令烏龜兔子指向起始節點,讓烏龜保持不動,兔子走2i 步, 即迭代一次,兔子走2步, 迭代2次,兔子走4步等等。在兔子走每一步後,判斷龜兔是否相遇,如果沒有相遇,則讓烏龜的位置變成兔子所在的位置,否則如果烏龜不動,則烏龜永遠無法進入環內,隨後進入新的迭代。迴圈下去,直至相遇或到表尾。

環長

由於烏龜一直處於兔子更改步長上限時的位置,所以更改步長上線後,兔子走了幾步與烏龜相遇,則環的長度就為多少。

環起點

Flyod判圈演算法利用了烏龜和兔子的距離是環長整數倍的性質求起點,所以可以讓烏龜回到起點,兔子回到距離起點環長距離的節點,隨後與Floyd演算法一樣

int *t = head, *h = head;
int steps_taken = 0, steps_limit = 2;
while(True)
{
    if(h == null)
        break; // No Loop
    ++steps_taken;
    if(h == t)
        break; // Found Loop;
    if(steps_taken == step_limit)
    {
        steps_taken = 0;
        step_limit *= 2;
        t = h; // teleport the t;
    }
}

演算法複雜度

時間複雜度

O(n), 線性複雜度,並且比Flyod 表現更好,且Flyod 是該演算法的最差表現

空間複雜度

O(1) 常數空間

參考資料