[轉載]鏈表的相交與環的問題

1、 給出兩個單向鏈表的頭指針pHead1和pHead2,判斷這兩個鏈表是否相交。假設兩個鏈表均不帶環。
 示意圖如下:

如果兩個鏈表相交於某一節點,那麼在這個相交節點之後的所有節點都是兩個鏈表所共有的。也就是說,如果兩個鏈表相交,那麼最後一個節點肯定是共有的。先遍歷第一個鏈表,記住最後一個節點,然後遍歷第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點做比較,如果相同,則相交,否則不相交。時間複雜度爲O( len1 + len2),因爲只需要一個額外指針保存最後一個節點地址,空間複雜度爲O(1)。(編程之美上面有詳細的介紹)
2、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環。
示意圖如下:

鏈表中有環,其實也就是自相交。我們用兩個指針pslow和pfast從頭開始遍歷鏈表,pslow每次前進一個節點,pfast每次前進兩個結點,若存在環,則pslow和pfast肯定會在環中相遇,若不存在,則pslow和pfast能正常到達最後一個節點(實際上是到達NULL)。
代碼如下:
  1. // 判斷鏈表中是否有環  
  2. bool IsExitLoop(LinkList *head)  
  3. {  
  4.     LinkList *pslow = head;  
  5.     LinkList *pfast = head;  
  6.     while(pfast != NULL && pfast->next != NULL)  
  7.     {  
  8.         pslow = pslow->next;        // 每次前進一步  
  9.         pfast = pfast->next->next;  // 每次前進二步  
  10.         if(pslow == pfast)          // 兩個指針相遇,說明存在環  
  11.             return true;  
  12.     }  
  13.     return false;    // 沒有環  
  14. }  
3、給出兩個單向鏈表的頭指針pHead1和pHead2,判斷這兩個鏈表是否相交,若相交返回第一個相交的節點。假設兩個鏈表均不帶環。
方法一
判斷兩個鏈表中是否存在地址一致的節點,就可以知道是否相交了。可以對第一個鏈表的節點地址進行hash排序,建立hash表,然後針對第二個鏈表的每個節點的地址查詢hash表,如果它在hash表中出現,則說明兩個鏈表有共同的結點。這個方法的時間複雜度爲:O(max(len1+len2);但同時還得增加O(len1)的存儲空間存儲哈希表。這樣減少了時間複雜度,增加了存儲空間。
以鏈表節點地址爲值,遍歷第一個鏈表,使用Hash保存所有節點地址值,結束條件爲到最後一個節點(無環)或Hash中該地址值已經存在(有環)。
方法二:
對第一個鏈表遍歷,計算長度len1,同時保存最後一個節點的地址。
對第二個鏈表遍歷,計算長度len2,同時檢查最後一個節點是否和第一個鏈表的最後一個節點相同,若不相同,則不相交,程序結束。
若相交,兩個鏈表均從頭節點開始,假設len1大於len2,那麼將第一個鏈表先遍歷len1-len2個節點,此時兩個鏈表當前節點到第一個相交節點的距離就相等了,比較下一個節點是不是相同,如果相同就返回該節點(即相交節點),若不相同,兩個鏈表都同步向後走一步,繼續比較。
示意圖如下:

方法三:
由於兩個鏈表都沒有環,我們可以把第二個鏈表接在第一個鏈表的後面,如果得到的鏈表有環,則說明這兩個鏈表相交。否則,這兩個鏈表不相交。這樣我們就把問題轉化爲判斷一個鏈表是否有環了。最後,當然可別忘記恢復原來的狀態,去掉從第一個鏈表到第二個鏈表表頭的指向。
4、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環,若存在,則求出進入環中的第一個節點。
 示意圖如下:

紅色虛線框中的節點爲待求節點。
首先使用第2個題目中的快、慢指針來判斷鏈表是否存在環,若不存在結束。
若鏈表中存在環,我們從鏈表頭、與兩個指針的相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇的第一個點爲環的入口點。

代碼如下:
  1. // 找到環的第一個入口點  
  2. LinkList* FindLoopPort(LinkList *head)  
  3. {  
  4.     LinkList *pslow = head;  
  5.     LinkList *pfast = head;  
  6.     while(pfast != NULL && pfast->next != NULL)  
  7.     {  
  8.         pslow = pslow->next;        // 每次前進一步  
  9.         pfast = pfast->next->next;  // 每次前進二步  
  10.         if(pslow == pfast)          // 兩個指針相遇,說明存在環  
  11.             break;  
  12.     }  
  13.     if(pfast == NULL || pfast->next == NULL)    // 不存在環  
  14.         return NULL;  
  15.     pslow = head;  
  16.     while(pslow != pfast)  
  17.     {  
  18.         pslow = pslow->next;        // 每次前進一步  
  19.         pfast = pfast->next;        // 每次前進一步  
  20.     }  
  21.     return pslow;       // 返回環的入口點  
  22. }  
分析:當pfast若與pslow相遇時,pslow肯定沒有走遍歷完鏈表,而pfast已經在環內循環了n圈(1<=n)。假設pslow走了s步,則pfast走了2s步(pfast步數還等於s 加上在環上多轉的n圈),設環長爲r,則:
 2s = s + nr    s= nr

設整個鏈表長L,入口環與相遇點距離爲x,起點到環入口點的距離爲a。   a + x = nr  則  a + x = (n – 1)r +r = (n-1)r + L - a   a = (n-1)r + (L – a – x)
(L – a – x)爲相遇點到環入口點的距離,由此可知,從鏈表頭到環入口點等於(n-1)循環內環+相遇點到環入口點,於是我們從鏈表頭、與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點爲環入口點。
小結:鏈表是數據結構中最基本的,也是面試中常考的,與鏈表相關的題目也變化多端,只要基礎紮實,多積累一些處理類似問題的技巧,面試時便能應對自如。

單鏈表的的歸併排序,同樣需要找到鏈表的中間節點,可以使用前面的這個快、慢指針的方法。
  1. typedef struct LNode  
  2. {  
  3.     int data;  
  4.     struct LNode *next;  
  5. }LNode , *LinkList;  
  6.   
  7. // 對兩個有序的鏈表進行遞歸的歸併  
  8. LinkList MergeList_recursive(LinkList head1 , LinkList head2)  
  9. {  
  10.     LinkList result;  
  11.     if(head1 == NULL)  
  12.         return head2;  
  13.     if(head2 == NULL)  
  14.         return head1;  
  15.     if(head1->data < head2->data)  
  16.     {  
  17.         result = head1;  
  18.         result->next = MergeList_recursive(head1->next , head2);  
  19.     }  
  20.     else  
  21.     {  
  22.         result = head2;  
  23.         result->next = MergeList_recursive(head1 , head2->next);  
  24.     }  
  25.     return result;  
  26. }  
  27.   
  28. // 對兩個有序的鏈表進行非遞歸的歸併  
  29. LinkList MergeList(LinkList head1 , LinkList head2)  
  30. {  
  31.     LinkList head , result = NULL;  
  32.     if(head1 == NULL)  
  33.         return head2;  
  34.     if(head2 == NULL)  
  35.         return head1;  
  36.     while(head1 && head2)  
  37.     {  
  38.         if(head1->data < head2->data)  
  39.         {  
  40.             if(result == NULL)  
  41.             {  
  42.                 head = result = head1;  
  43.                 head1 = head1->next;  
  44.             }  
  45.             else  
  46.             {  
  47.                 result->next = head1;  
  48.                 result = head1;  
  49.                 head1 = head1->next;  
  50.             }  
  51.         }  
  52.         else  
  53.         {  
  54.             if(result == NULL)  
  55.             {  
  56.                 head = result = head2;  
  57.                 head2 = head2->next;  
  58.             }  
  59.             else  
  60.             {  
  61.                 result->next = head2;  
  62.                 result = head2;  
  63.                 head2 = head2->next;  
  64.             }  
  65.         }  
  66.     }  
  67.     if(head1)  
  68.         result->next = head1;  
  69.     if(head2)  
  70.         result->next = head2;  
  71.     return head;  
  72. }  
  73.   
  74. // 歸併排序,參數爲要排序的鏈表的頭結點,函數返回值爲排序後的鏈表的頭結點  
  75. LinkList MergeSort(LinkList head)  
  76. {  
  77.     if(head == NULL)  
  78.         return NULL;  
  79.     LinkList r_head , slow , fast;  
  80.     r_head = slow = fast = head;  
  81.   
  82.     // 找鏈表中間節點的兩種方法  
  83.     /* 
  84.     while(fast->next != NULL) 
  85.     { 
  86.         if(fast->next->next != NULL) 
  87.         { 
  88.             slow = slow->next; 
  89.             fast = fast->next->next; 
  90.         } 
  91.         else 
  92.             fast = fast->next; 
  93.     }*/  
  94.   
  95.     while(fast->next != NULL && fast->next->next != NULL)  
  96.     {  
  97.         slow = slow->next;  
  98.         fast = fast->next->next;  
  99.     }  
  100.       
  101.     if(slow->next == NULL)    // 鏈表中只有一個節點  
  102.         return r_head;  
  103.     fast = slow->next;  
  104.     slow->next = NULL;  
  105.     slow = head;  
  106.   
  107.     // 函數MergeList是對兩個有序鏈表進行歸併,返回值是歸併後的鏈表的頭結點  
  108.     //r_head = MergeList_recursive(MergeSort(slow) , MergeSort(fast));  
  109.     r_head = MergeList(MergeSort(slow) , MergeSort(fast));  
  110.     return r_head;  
  111. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章