題目:如何判斷單鏈表里面是否有環?
方法一:快慢指針法
設兩個工作指針,一個快一個慢,如果有環的話,它們會必然在某點相遇。
為什么當單鏈表存在環時,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