鏈表檢測環以及環的入口

題目爲:對於普通的單向鏈表,如果實現確定其內部有一個環,如何確定何處出現環路的?單向鏈表每個節點中只有data和next兩個字段。

      (單向鏈表含環路,不要總是想到“0”型環路,還要想到“6”字型環路)

      原本聽到這道題時,我首先想到的笨辦法就是:建一個足夠大的一維數組,,每個位置放Node*類型指針。而後開始遍歷單向鏈表,遍歷過一個節點後就將該節點的指針添加到這個一維數組中,隨後與該數組前邊的所有元素進行一次遍歷比較,如果有重複,則定位到了這個出現環路的節點。

      但是後來面試官說:這個空間複雜度有點大,如果場景是有幾百萬條記錄呢?有沒有辦法大大的降低這個時間複雜度?    因爲是電面的,自己一時也沒想出什麼好辦法來,慚愧慚愧~今天一早請教了下龍哥,龍哥給出了一個不錯的思路,我測了一下,沒有問題。

      主體思路是:

      從頭結點開始遍歷整個鏈表,沒遍歷過一個節點:就將其next置爲NULL.這樣:當往後遍歷到某個節點:其next指向節點的next爲NULL時變找到了。 注意:①很多人看到後會說:你這樣不是破壞了原先的單向鏈表了嗎?的確是這樣,所以在考慮這種算法時還要同時考慮該如何進行恢復!最好是:使用完了之後接着恢復。而要做到這一點只能用遞歸來實現。(不過用遞歸貌似還是很大空間複雜度)

  1. struct Node     
  2. {     
  3.     int data;     
  4.     int pNext;     
  5. };     
  6.     
  7. void Fun(Node* pData,int currentPos,int& callbackPos)     
  8. {     
  9.     if(pData[currentPos].pNext)     
  10.     {     
  11.         int backup = pData[currentPos].pNext;     //記錄下後繼節點  
  12.         pData[currentPos].pNext = NULL;     
  13.         Fun(pData,backup,callbackPos);     
  14.         pData[currentPos].pNext = backup;     //恢復後繼節點  
  15.     }     
  16.     else    
  17.     {     
  18.         callbackPos = currentPos;     
  19.     }     
  20. }     
  21. int main()     
  22. {     
  23.     //initilze array:     
  24.     Node* pData = new Node[7];     
  25.     for(int i=0;i<6;i++)     
  26.     {     
  27.         pData[i].pNext = i+1;     
  28.     }     
  29.     pData[6].pNext = 3;     
  30.     
  31.     for(int i=0;i<7;i++)     
  32.     {     
  33.         pData[i].data = 10;     
  34.     }     
  35.     //-----------------------     
  36.     int callback = -1;     
  37.     Fun(pData,0,callback);     
  38.     printf("%d/n", callback);     
  39.     system("pause");     
  40.          return 0;     
  41. }    
    

    所以:有時候遞歸用來處理這種既需要全局變化,又需要恢復的算法時很有用。


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/NRC_DouNingBo/archive/2010/06/23/5688868.aspx

 

還有另外一篇介紹的方法,比這個方法要簡答

有一個單向鏈表,如何判定這個鏈表當中是否包含有環路,以及如何定位環路在鏈表當中的開始點呢?

關於第一個問題,網絡上可以搜索到,用兩個指針來遍歷這個單向鏈表,第一個指針p1,每次走一步;第二個指針p2,每次走兩步;  當p2 指針追上 p1的時候,就表明鏈表當中有環路了。

關於這個解法最形象的比喻就是在操場當中跑步,速度快的會把速度慢的扣圈

可以證明,p2追趕上p1的時候,p1一定還沒有走完一遍環路,p2也不會跨越p1多圈才追上

我們可以從p2和p1的位置差距來證明,p2一定會趕上p1但是不會跳過p1的

因爲p2每次走2步,而p1走一步,所以他們之間的差距是一步一步的縮小,4,3,2,1,0 到0的時候就重合了

根據這個方式,可以證明,p2每次走三步以上,並不總能加快檢測的速度,反而有可能判別不出有環

比如,在環的周長L是偶數的時候,初始p2和p1相差奇數的時候,p2每次走三步,就永遠和p1不重合,因爲他們之間的差距是:  5, 3 , 1,  L-1, L-3


關於第二個問題,如何找到環路的入口,是這裏要重點說明的內容:

解法如下: 當p2按照每次2步,p1每次一步的方式走,發現p2和p1重合,確定了單向鏈表有環路了

接下來,讓p2回到鏈表的頭部,重新走,每次步長不是走2了,而是走1,那麼當p1和p2再次相遇的時候,就是環路的入口了。

這點可以證明的:

在p2和p1第一次相遇的時候,假定p1走了n步驟,環路的入口是在p步的時候經過的,那麼有

p1走的路徑: p+c = n;         c爲p1和p2相交點,距離環路入口的距離

p2走的路徑: p+c+k*L = 2*N;   L爲環路的周長,k是整數

顯然,如果從p+c點開始,p1再走n步驟的話,還可以回到p+c這個點

同時p2從頭開始走的話,經過n不,也會達到p+c這點

顯然在這個步驟當中p1和p2只有前p步驟走的路徑不同,所以當p1和p2再次重合的時候,必然是在鏈表的環路入口點上

  1. #include "iostream.h"  
  2. #include "memory.h"  
  3. #include "new.h"  
  4. class CList {  
  5. public:  
  6.  int nData;  
  7.  CList * pNext;  
  8. } * pRoot = NULL;  
  9.   
  10. const int size = sizeof(CList) / sizeof(int);  
  11. int  buffer[101*size];  
  12. bool Init(int n)  
  13. {  
  14.     pRoot = (CList*)buffer;  
  15.  if ( n<1 && n>98 )  return false;  
  16.  CList * pTemp = NULL;  
  17.  for ( int i=0; i<101; i++ ) {  
  18.     pTemp = new (buffer+i*size) CList();  
  19.     pTemp->nData = i;  
  20.     pTemp->pNext = (CList *)(buffer + (i+1)*size);  
  21.  };  
  22.  pTemp->pNext = (CList *) (buffer + n*size);  
  23.  return true;  
  24. }  
  25.   
  26. void ClearCircle(CList * pRoot)  
  27. {  
  28.  CList * p1, * p2;  
  29.  p1 = p2 = pRoot;  
  30.  do {  
  31.      p2 = p2->pNext->pNext;  
  32.   p1 = p1->pNext;  
  33.  } while ( p2!=NULL && p1!=p2); //這裏有問題吧,如果p2->pNext爲NULL,p2->pNext->pNext就指向未知位置了,有危險,所以應該加上 p2->pNext!=NULL判斷條件
  34.  if ( p1 == p2 ) {  
  35.   p2 = pRoot;  
  36.   while (1) {  
  37.    p2 = p2->pNext;  
  38.    if ( p1->pNext == p2 ) break;  
  39.    p1 = p1->pNext;  
  40.   }   
  41.   p1->pNext = NULL;  
  42.  }  
  43. }  
  44.   
  45. main()  
  46. {  
  47.     CList * pList = pRoot;  
  48.  if (Init(21) )   
  49.  {  
  50.   cout << "Before clear:!" << "/r/n";  
  51.   pList = pRoot;  
  52.   for ( int i=0; i<104; i++)  
  53.   {  
  54.    cout << pList->nData << "/r/n";  
  55.    pList = pList->pNext;  
  56.   }  
  57.   ClearCircle(pRoot);  
  58.  }  
  59.  cout << "After clear:" << "/r/n";  
  60.  pList = pRoot;  
  61.  while (pList) {  
  62.   cout << pList->nData << "/r/n";  
  63.   pList = pList->pNext;  
  64.  }  
  65.  return 0;  
  66. }  


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/happy__888/archive/2005/12/21/558356.aspx

轉自:http://blog.csdn.net/seanyxie/article/details/5825261

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章