1. 程式人生 > >Floyd判圈演算法(龜兔賽跑演算法)

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;  
}