判斷單鏈表是否有環


題目:如何判斷鏈表里面是否有環?

 

方法一:快慢指針法

設兩個工作指針,一個快一個慢,如果有環的話,它們會必然在某點相遇。

為什么當單鏈表存在環時,p和q一定會相遇呢?


假定單鏈表的長度為n,並且該單鏈表是環狀的,那么第i次迭代時,p指向元素i mod n,q指向2i mod n。因此當i≡2i(mod n)時,p與q相遇。而i≡2i(mod n) => (2i - i) mod n = 0 => i mod n = 0 => 當i=n時,p與q相遇。這里一個簡單的理解是,p和q同時在操場跑步,其中q的速度是p的兩倍,當他們兩個同時出發時,p跑一圈到達起點,而q此時也剛好跑完兩圈到達起點。
那么當p與q起點不同呢?假定第i次迭代時p指向元素i mod n,q指向k+2i mod n,其中0<k<n。那么i≡(2i+k)(mod n) => (i+k) mod n = 0 => 當i=n-k時,p與q相遇。

擴展:

1. 如果兩個指針的速度不一樣,比如p,q,( 0<p<q)二者滿足什么樣的關系,可以使得兩者肯定交與一個節點?

    Sp(i) = pi

    Sq(i) = k + qi

   如果兩個要相交於一個節點,則 Sp(i) = Sq(i) =>  (pi) mod n = ( k+ qi ) mod n =>[ (q -p)i + k ]  mod n =0

   =>  (q-p)i + k  = Nn [N 為自然數]

   =>  i = (Nn -k) /(p-q)

   i取自然數,則當 p,q滿足上面等式 即 存在一個自然數N,可以滿足Nn -k 是 p - q 的倍數時,保證兩者相交。

   特例:如果q 是p 的步長的兩倍,都從同一個起點開始,即 q = 2p , k =0, 那么等式變為: Nn=i: 即可以理解為,當第i次迭代時,i是圈的整數倍時,兩者都可以交,交點就是為起點。

2.如何判斷單鏈表的環的長度?

記錄下問題1的碰撞點p,slow、fast從該點開始,再次碰撞所走過的操作數就是環的長度s。

3. 如何找到鏈表中第一個在環里的節點?

   假設鏈表長度是L,前半部分長度為k-1,那么第一個再環里的節點是k,環的長度是 n, 那么當q=2p時, 什么時候第一次相交呢?當q指針走到第k個節點時,q指針已經在環的第 k mod n 的位置。即p和q 相差k個元素,從不同的起點開始,則相交的位置為 n-k, 則有了下面的圖:

從圖上可以明顯看到,當p從交點的位置(n-k) ,向前遍歷k個節點就到到達環的第一個幾點,節點k.

算法就很簡單: 一個指針從p和q 中的第一次相交的位置起(n-k),另外一個指針從鏈表頭開始遍歷,其交點就是鏈表中第一個在環里的交點

 

也有大神說這里其實是一個定理:碰撞點p到連接點的距離=頭指針到連接點的距離,因此,分別從碰撞點、頭指針開始走,相遇的那個點就是連接點。

實際上和我們上面分析的是一個意思,要理解着來還是背下來就隨便你咯,定理證明見:http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html

4、帶環鏈表的長度是多少?

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

4. 如果判斷兩個單鏈表有交?第一個交點在哪里?

 

 1 bool IsExitsLoop(slist *head)  
 2 {  
 3     slist *slow = head, *fast = head;  
 4   
 5     while ( fast && fast->next )   
 6     {  
 7         slow = slow->next;  
 8         fast = fast->next->next;  
 9         if ( slow == fast ) break;  
10     }  
11   
12     return !(fast == NULL || fast->next == NULL);  
13 } 

 

找到交點的思路是把其中鏈表一個鏈表尾節點與頭節點相連,如果有環,則很容發現問題轉化為問題3,求有環的鏈表的第一個在環里的節點。

找到環點:

 1 slist* FindLoopPort(slist *head)  
 2 {  
 3     slist *slow = head, *fast = head;  
 4   
 5     while ( fast && fast->next )   
 6     {  
 7         slow = slow->next;  
 8         fast = fast->next->next;  
 9         if ( slow == fast ) break;  
10     }  
11   
12     if (fast == NULL || fast->next == NULL)  
13         return NULL;  
14   
15     slow = head;  
16     while (slow != fast)  
17     {  
18          slow = slow->next;  
19          fast = fast->next;  
20     }  
21   
22     return slow;  
23 }  

 方法二.

設兩個工作指針p、q,p總是向前走,但q每次都從頭開始走,對於每個節點,看p走的步數是否和q一樣。比如p從A走到D,用了4步,而q則用了14步。因而步數不等,出現矛盾,存在環.

//if two pointer are equal, but they don't have the same steps, then has a loop
02
int HasLoop(LinkList L)
03
{
04
    LinkList cur1 = L;  // 定義結點 cur1
05
    int pos1 = 0;       // cur1 的步數
06
    while(cur1){        // cur1 結點存在
07
        LinkList cur2 = L;  // 定義結點 cur2
08
        int pos2 = 0;       // cur2 的步數
09
        pos1 ++;            // cur1 步數自增
10
        while(cur2){        // cur2 結點不為空
11
            pos2 ++;        // cur2 步數自增
12
            if(cur2 == cur1){   // 當cur1與cur2到達相同結點時
13
                if(pos1 == pos2)    // 走過的步數一樣
14
                    break;          // 說明沒有還
15
                else                // 否則
16
                    return 1;       // 有環並返回1
17
            }
18
            cur2 = cur2->next;      //  如果沒發現環,繼續下一個結點
19
        }
20
        cur1 = cur1->next;  // cur1繼續向后一個結點
21
    }
22
    return 0;
23
}

方法三

在環的入口點出斷開,從而轉換為看兩個鏈表是否有交點的問題(改日補充)

參考資料:http://blog.csdn.net/yiwuxue/article/details/21973079

       http://blog.csdn.net/cuit/article/details/35365219

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM