1. 程式人生 > >資料結構和演算法設計專題之---判斷單鏈表中是否有環,環的長度,環的入口節點

資料結構和演算法設計專題之---判斷單鏈表中是否有環,環的長度,環的入口節點

題目:

給定一個單鏈表,只給出頭指標head:

1、如何判斷是否存在環?
2、如何知道環的長度?
3、如何找出環的連線點在哪裡?
4、帶環連結串列的長度是多少?

 
解法:

1、對於問題1,使用追趕的方法,設定兩個指標slow、fast,從頭指標開始,每次分別前進1步、2步。如存在環,則兩者相遇;如不存在環,fast遇到NULL退出。

2、對於問題2,記錄下問題1的碰撞點p,slow、fast從該點開始,再次碰撞所走過的運算元就是環的長度s。

3、問題3:有定理:碰撞點p到連線點的距離=頭指標到連線點的距離,因此,分別從碰撞點、頭指標開始走,相遇的那個點就是連線點。(證明在後面附註)

4、問題3中已經求出連線點距離頭指標的長度,加上問題2中求出的環的長度,二者之和就是帶環單鏈表的長度

判斷是否存在環的程式:
bool IsExitsLoop(slist *head)  
{  
    slist *slow = head, *fast = head;  
    while ( fast && fast->next )   
    {  
        slow = slow->next;  
        fast = fast->next->next;  
        if ( slow == fast ) break;  
    }    
    return !(fast == NULL || fast->next == NULL);  
}  

尋找環連線點(入口點)的程式:
slist* FindLoopPort(slist *head)  
{  
    slist *slow = head, *fast = head;    
    while ( fast && fast->next )   
    {  
        slow = slow->next;  
        fast = fast->next->next;  
        if ( slow == fast ) break;  
    }    
    if (fast == NULL || fast->next == NULL)  
        return NULL;  
    slow = head;  
    while (slow != fast)  
    {  
         slow = slow->next;  
         fast = fast->next;  
    }  
    return slow;  
} 

亦可以用類似與hash表的方法,即設立一個數組,將連結串列結點中的值做陣列下標,當賦值衝突時就是環的接入點

證明題:

對於一個順時針的環,有P,Q兩點,且兩點相距為N,同時,P每向前移動一步,Q就向前以東兩步,已知環的周長是L,問P,Q兩點相遇在哪點上?如下圖所示:P點是環的入口點


假設P,Q兩點相遇時,P點移動的距離是X,則有以下等式:

X-N = 2*X - L;

=>X= L-N

那麼我可以從上圖看到相遇點離入口點的距離為:L-(L-N)=N

綜上所述:
假設單鏈表的總長度為L,頭結點到環入口的距離為a,環入口到快慢指標相遇的結點距離為x,環的長度為r,慢指標總共走了s步,則快指標走了2s步。另外,快指標要追上慢指標的話快指標至少要在環裡面轉了一圈多(假設轉了n圈加x的距離),得到以下關係:
    s = a + x
    2s = a + nr + x
    =>a + x = nr
    =>a = nr - x

由上式可知:若在頭結點和相遇結點分別設一指標,同步(單步)前進,則最後一定相遇在環入口結點

通過上面的證明題發現數學知識在程式設計世界中真的很重要呀~~

總結:我們看到這裡面有一個核心的地方就是第一個問題,判斷有沒有環的情況,採用了兩個指標:快指標和慢指標,這個在處理一些連結串列的問題中尤其重要,比如下面的兩道關於連結串列的題目:

第一題:已知單鏈表的頭指標,查詢到倒數第K個節點

這道題的通俗的解法就是先遍歷一邊連結串列,得到連結串列的長度N,然後再從頭開始遍歷N-K個節點即可

但是現在如果要求只遍歷一遍連結串列的話,該怎麼操作呢?

這時候就可以藉助快指標和慢指標了

我們定義一個快指標P和慢指標Q,先讓P指標走到K個節點位置,然後Q指標從頭指標開始和P一起移動,當P移動到尾部的時候,那麼此時Q節點所在的位置就是倒數第K個節點。

第二題:已知單鏈表的頭結點,查詢到連結串列的中間節點

這道題的通俗的解法和上面的方法一樣,就是先遍歷一邊連結串列,得到連結串列的長度N,然後再次遍歷N/2個節點即可

但是現在同樣的如果要求之遍歷一邊連結串列的話,該怎麼操作呢?

這時候我們同樣可以藉助快指標和慢指標了

我們定義一個快指標P和慢指標Q,P和Q同時從頭指標出發,快指標P每次移動兩步,慢指標每次移動一步,當快指標P到尾部的時候,慢指標Q所在的位置就是中間節點的位置。

通過上面的兩道題目我們可以看到快慢指標的在解決單鏈表的相關問題上還是很有用的~~

下面在來看一下還有一個技巧就是在解決第二道題目的時候,那個求環的入口節點,這個當時真的是沒有任何頭緒,所以這時候就要求我們又很好的數學功底了,能夠發現相關的規律,然後總結出定理,這樣就可以將問題簡化了。