判圈演算法(Flyod、Brent's)
問題
如何檢測一個連結串列是否有環?如果有環,如何確定環的起點以及長度?
Floyd cycle detection(龜兔演算法)
龜兔賽跑
對於賽道來說,如果賽道中有環,那麼速度快的兔子一定會在某個地點追上烏龜,並且兔子所跑的距離減去烏龜所跑的距離,一定是環長度的整數倍。
原理
假設令龜、兔為指標,並且指向起點位置,兔子每次移動兩個節點,烏龜每次移動一個節點。如果兩者在起始節點外相遇,則說明有環。如果兔子在走到連結串列尾部還沒有與烏龜相遇,說明無環。
環長度
通過上述演算法判斷出存在環C時,顯然龜兔位於同一節點M,此時,令兔子保持不動,而烏龜不斷推進,記錄移動距離,等再次相遇時,移動步數即環C長度。
環起點
只要令兔子仍位於相遇節點M, 而令烏龜返回連結串列起始節點S,此時烏龜與兔子之間的距離為環C長度的整數倍。隨後,同時讓烏龜與兔子不斷推進一步,直至相遇同一節點P,則節點P即為從連結串列起始節點所到達的環C的第一個節點,即環C起點。
分析
如圖所示,令起始節點為h
, 起始節點到環起點距離為m
, 龜兔相遇節點為p
, p
節點與環起點距離為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
為環長整數倍,故兔子移動到了環起點,即相遇節點為環起點。 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)
原理
起始令烏龜兔子指向起始節點,讓烏龜保持不動,兔子走 步, 即迭代一次,兔子走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) 常數空間