題目爲:對於普通的單向鏈表,如果實現確定其內部有一個環,如何確定何處出現環路的?單向鏈表每個節點中只有data和next兩個字段。
(單向鏈表含環路,不要總是想到“0”型環路,還要想到“6”字型環路)
原本聽到這道題時,我首先想到的笨辦法就是:建一個足夠大的一維數組,,每個位置放Node*類型指針。而後開始遍歷單向鏈表,遍歷過一個節點後就將該節點的指針添加到這個一維數組中,隨後與該數組前邊的所有元素進行一次遍歷比較,如果有重複,則定位到了這個出現環路的節點。
但是後來面試官說:這個空間複雜度有點大,如果場景是有幾百萬條記錄呢?有沒有辦法大大的降低這個時間複雜度? 因爲是電面的,自己一時也沒想出什麼好辦法來,慚愧慚愧~今天一早請教了下龍哥,龍哥給出了一個不錯的思路,我測了一下,沒有問題。
主體思路是:
從頭結點開始遍歷整個鏈表,沒遍歷過一個節點:就將其next置爲NULL.這樣:當往後遍歷到某個節點:其next指向節點的next爲NULL時變找到了。 注意:①很多人看到後會說:你這樣不是破壞了原先的單向鏈表了嗎?的確是這樣,所以在考慮這種算法時還要同時考慮該如何進行恢復!最好是:使用完了之後接着恢復。而要做到這一點只能用遞歸來實現。(不過用遞歸貌似還是很大空間複雜度)
- struct Node
- {
- int data;
- int pNext;
- };
- void Fun(Node* pData,int currentPos,int& callbackPos)
- {
- if(pData[currentPos].pNext)
- {
- int backup = pData[currentPos].pNext; //記錄下後繼節點
- pData[currentPos].pNext = NULL;
- Fun(pData,backup,callbackPos);
- pData[currentPos].pNext = backup; //恢復後繼節點
- }
- else
- {
- callbackPos = currentPos;
- }
- }
- int main()
- {
- //initilze array:
- Node* pData = new Node[7];
- for(int i=0;i<6;i++)
- {
- pData[i].pNext = i+1;
- }
- pData[6].pNext = 3;
- for(int i=0;i<7;i++)
- {
- pData[i].data = 10;
- }
- //-----------------------
- int callback = -1;
- Fun(pData,0,callback);
- printf("%d/n", callback);
- system("pause");
- return 0;
- }
所以:有時候遞歸用來處理這種既需要全局變化,又需要恢復的算法時很有用。
本文來自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再次重合的時候,必然是在鏈表的環路入口點上
- #include "iostream.h"
- #include "memory.h"
- #include "new.h"
- class CList {
- public:
- int nData;
- CList * pNext;
- } * pRoot = NULL;
- const int size = sizeof(CList) / sizeof(int);
- int buffer[101*size];
- bool Init(int n)
- {
- pRoot = (CList*)buffer;
- if ( n<1 && n>98 ) return false;
- CList * pTemp = NULL;
- for ( int i=0; i<101; i++ ) {
- pTemp = new (buffer+i*size) CList();
- pTemp->nData = i;
- pTemp->pNext = (CList *)(buffer + (i+1)*size);
- };
- pTemp->pNext = (CList *) (buffer + n*size);
- return true;
- }
- void ClearCircle(CList * pRoot)
- {
- CList * p1, * p2;
- p1 = p2 = pRoot;
- do {
- p2 = p2->pNext->pNext;
- p1 = p1->pNext;
- } while ( p2!=NULL && p1!=p2); //這裏有問題吧,如果p2->pNext爲NULL,p2->pNext->pNext就指向未知位置了,有危險,所以應該加上 p2->pNext!=NULL判斷條件
- if ( p1 == p2 ) {
- p2 = pRoot;
- while (1) {
- p2 = p2->pNext;
- if ( p1->pNext == p2 ) break;
- p1 = p1->pNext;
- }
- p1->pNext = NULL;
- }
- }
- main()
- {
- CList * pList = pRoot;
- if (Init(21) )
- {
- cout << "Before clear:!" << "/r/n";
- pList = pRoot;
- for ( int i=0; i<104; i++)
- {
- cout << pList->nData << "/r/n";
- pList = pList->pNext;
- }
- ClearCircle(pRoot);
- }
- cout << "After clear:" << "/r/n";
- pList = pRoot;
- while (pList) {
- cout << pList->nData << "/r/n";
- pList = pList->pNext;
- }
- return 0;
- }
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/happy__888/archive/2005/12/21/558356.aspx