Floyd判圈演算法(龜兔賽跑演算法)
一、演算法簡述
Floyd判圈演算法(Floyd Cycle Detection Algorithm),又稱龜兔賽跑演算法(Tortoise and Hare Algorithm),是一個可以在有限狀態機、迭代函式或者連結串列上判斷是否存在環,以及判斷環的起點與長度的演算法。
二、基本思路
在某種關係下,頂點 i 到 k 拓撲有序,頂點 k 到 j 也是相同的順序,那麼 i 和 j 也存在這個順序。要是某一個頂點出現了自己到自己的環,那麼圖中就有環,但是這種方法複雜度高一些,沒有檢測頂點出度或者DFS的方法快,但是非常簡單。
三、問題
如何檢測一個連結串列是否有環,如果有,那麼如何確定環的起點和環的長度。
四、解決方案
(1)判斷是否有環?
龜兔解法的基本思想可以用我們跑步的例子來解釋,如果兩個人同時出發,如果賽道有環,那麼快的一方總能追上慢的一方。進一步想,追上時快的一方肯定比慢的一方多跑了幾圈,即多跑的路的長度是圈的長度的倍數。
基於上面的想法,Floyd用兩個指標,一個慢指標(龜)每次前進一步,快指標(兔)指標每次前進兩步(兩步或多步效果時等價的,只要一個比另一個快就行)。如果兩者在連結串列頭以外的某一點相遇(即相等)了,那麼說明連結串列有環,否則,如果(快指標)到達了連結串列的結尾,那麼說明沒壞。
(2)求環的長度
相遇的時候,一定已經在環上了,然後兩個人只要再次在環上接著跑,再次相遇的時候(也就是所謂的套圈),跑的快的那個人就比跑的慢的人整整多跑了一圈,所以環的長度也就出來了。
(3)如何確定環的起點
環的檢測用上述原理,接下來我們來看一下如何確定環的起點,這也是Floyd解法的第二部分。方法是將其中一個指標移到連結串列起點,兩者同時移動,每次移動一步,那麼兩者相遇的地方就是環的起點。
解析:
首先假設第一次相遇的時候慢指標走過的節點個數為i,設連結串列頭部到環的起點的長度為m,環的長度為n,相遇的位置與起點與起點位置距離為k。
於是有:
i = m + a * n + k
其中a為慢指標走的圈數。
因為快指標的速度是慢指標的2倍,於是又可以得到另一個式子:
2 * i = m + b * n + k
其中b為快指標走的圈數。
兩式相減得:
i = ( b - a ) * n
也就是說i是圈長的整數倍。
這是將其中一個節點放在起點,然後同時向前走m步時,此時從頭部走的指標在m位置。而從相遇位置開始走的指標應該在距離起點i+m,i為圈長整數倍,則該指標也應該在距離起點為m的位置,即環的起點。
例項程式如下:
int *head = list.GetHead();
if (head != null) {
int *fastPtr = head;
int *slowPtr = head;
bool isCircular = true;
do
{
if (fastPtr->Next == null || fastPtr->Next->Next == null) //List end found
{
isCircular = false;
break;
}
fastPtr = fastPtr->Next->Next;
slowPtr = slowPtr->Next;
} while (fastPtr != slowPtr);
//確定環的起點
slowPtr = head;
while(slowPtr != fastPtr)
{
slowPtr = slowPtr->Next;
fastPtr = fastPtr->Next;
}
cout<<"the starting point of the cycle is "<<slowPtr<<endl;
}