4 反轉單向連結串列(非遞迴實現)
思路:
圖1 非遞迴反轉連結串列
如圖1所示,假設已經反轉了前面的若干節點,且前一段連結串列的頭節點指標為pre,則現在要做的事情是首先儲存當前節點cur後面的連結串列,然後讓當前節點cur的指標與後面的節點斷開(step1),接下來再將當前節點的next指標指向前一段連結串列的頭節點pre (step2)。處理完當前節點的連線反轉後,所有的指標都向後移一位。開始處理下一個節點。
注意點:
- 反轉後原來的頭節點就變成了反轉連結串列的尾節點,要注意將此結點next指標設為空,否則可能會產生死迴圈等問題
- 要記得處理連結串列中沒有節點或只有一個的情況。
程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//反轉連結串列(非遞迴的方式) //輸入引數:單連結串列的頭指標 //輸出引數:無 //返回值:反轉後的單連結串列指標 SingleList* Reverse_NRecu(SingleList *head) { SingleList *pre,*cur,*lat; //連結串列中沒有節點或只有一個節點 if((head == NULL)||(head->next == NULL)) { return head; } pre = head; cur = head->next; head->next = NULL;//在連結串列中應該注意邊界情況的處理,尾結點一定要為空 lat = cur->next; while(lat != NULL) { cur->next = pre; pre = cur; cur = lat; lat = lat->next; } cur->next = pre; return cur; } |
5 反轉單向連結串列(遞迴實現)
思路:
在用遞迴實現問題時,要時刻注意在分析時我們只要重點分析第一層和第二層之間的關係就可以了,不要隨著遞迴一直向下思考。如果考慮的層次太多,可能會陷入進行而越想越亂。遞迴中另一個要特別注意的是,遞迴的終止條件。一定要把什麼時候結束、怎麼結束想清楚。
對於連結串列的反轉:
圖2 遞迴實現反轉連結串列
考慮第一層和第二層之間的關係時,我們假定第二層後面的連結串列已經處理完畢了,它返回了後面連結串列的尾指標,如圖2所示(藍色框包含的節點是後繼要遞迴的節點,在考慮第一層時我們假定它們已經反轉成功了)。這時我們只要將返回的指標cur的next指向當前指標,就完成了當前節點的反轉。反轉後新加入反轉鏈的指標pre返回即可。
另一個要考慮的就是遞迴的終止條件,問題的終止是當遞迴到最後一個節點時,它後面已經沒有節點,這是直接返回這可以了。
程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//反轉連結串列(遞迴的方式) //輸入引數:單連結串列的頭指標 //輸出引數:反轉連結串列後的新的指標 //返回值:反轉後單連結串列的最後一個指標 SingleList* Reverse_Recu(SingleList *head,SingleList **newHead) { SingleList *pSL; //終止條件 if((head == NULL)||(head->next == NULL)) { *newHead = head; return head; } pSL = Reverse_Recu(head->next,newHead); pSL->next = head; head->next = NULL; return head; } |
6 判斷單向連結串列是否有環
思路:
圖3 帶環的單向連結串列
一般看到這個問題,我們都能想到簡單方法可能是,逐個節點地遍歷連結串列,並記錄遍歷過節點的地址,在遍歷下一個節點時判斷其是否在已經遍歷過的節點集合中,如果在則說明連結串列有環。這種方法的時間複雜度是O(n2)。
我在網上查到一個更高效的方法。它與前面第3題的方法類似,設定兩個遍歷指標,第一個指標firstPtr每次走兩步,第二個指標每次走一步,這樣如果兩個指標能相遇則說明連結串列有環。
分析可得,如果連結串列有環,則在第二個指標第一次到達環的入口節點時,第一個指標肯定已經在環內的某個節點上了,接下來這兩個指標就在連結串列的環內旋轉遍歷,因為第一個指標比第二個指標每次都多走一步,所以它肯定會追上第二個指標。
對於這裡是不是一定能追上,我想應該有一個嚴格的證明,假設環上總共有n個節點,分別標號為0到n-1,如圖3所示,並且第二個指標第一次到達環的入口節點時,第一個指標指向節點a, 則如果它們走了i 次後相遇,即i%n == (a + 2*i)*/n,則說明連結串列有環。
因此,兩個指標是不是一定能在環內相遇的問題就轉化成了,證明存在一個正整數i,使i%n == (a + 2*i)*/n,其中(n>=2, a>=0並且a<n)的問題。由於我數論不好,對此問題一直沒證明出來,所以在此請高手指教?
用程式碼實現的方法,證實它確定可以判斷出連結串列是否有環。
程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//判斷單向連結串列是否有環 //輸入引數:單連結串列的頭指標 //輸出引數:無 //返回值:有環返回true,無環返回false bool IsLoop(SingleList *head) { SingleList *firstPtr,*secondPtr; firstPtr = head; secondPtr = head; while(firstPtr&&firstPtr->next) { firstPtr = firstPtr->next->next; secondPtr = secondPtr->next; if(firstPtr == secondPtr) { return true; } } return false; } |
7 判斷兩個單向連結串列是否相交
思路:
圖4 相交的單向連結串列
考慮如果兩個單向連結串列相交(如圖4所示),那麼兩個連結串列中肯定有共同的節點。
針對此問題, 最一般的思路就是判斷一個連結串列的節點是否在另一個連結串列中存在。這種方法要雙層迴圈實現,所以時間的複雜度為O(n2)。
進一步研究相交連結串列的特性,發現如果相交,則兩條連結串列的最後一個節點肯定相同。因此,只要判斷兩條連結串列的最後一個節點是不是相同就可以了。這種方法只需要遍歷一遍每個連結串列找到它們的最終節點就可以了,所以演算法的複雜度為O(n)。
程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//判斷兩條單向連結串列是否相交 //輸入引數:兩個單連結串列的頭指標 //輸出引數:無 //返回值:相交返回true,不相交返回false bool IsIntersect(SingleList *head1,SingleList *head2) { SingleList *firstPtr = head1,*secondPtr = head2; if((firstPtr == NULL) || (secondPtr == NULL)) { return false; } //迴圈遍歷第一個連結串列,找到最後一個元素 while(firstPtr->next) { firstPtr = firstPtr->next; } //迴圈遍歷第二個連結串列,找到最後一個元素 while(secondPtr->next) { secondPtr = secondPtr->next; } if(firstPtr == secondPtr) { return true; } return false; } |
擴充套件問題:返回兩個連結串列的第一個交點?
仔細閱讀上一個問題的思路,可發現思路中第一種解決方法就可以解決這個問題。但其時間的複雜度為O(n2)。那麼能不能仿照第二種方法一樣來提高演算法的效率呢?答案,當然是可以。
分析兩個相交連結串列的性質可知,如果相交,則交點之後的連結串列節點同時屬於這兩個連結串列。由此可以推斷出,交點之後每條連結串列上節點的個數肯定是相同的。因此,如果兩條連結串列節點的個數分別為len1和len2(len1>len2),那麼他們的第一個交點在第一條連結串列上肯定是第(len1-len2)個節點之後的某個節點。
總結上面的分析,我們得出一演算法:
- 先分別遍歷一遍兩條連結串列,求出兩連結串列各自的節點個數len1和len2。
- 讓節點多的連結串列先走|len1-len2|
- 兩條連結串列同時向後步進,並判斷節點是否相同。第一個相同點就是第一個交點。
程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
//求兩條單向連結串列的第一個交點 //輸入引數:兩個單連結串列的頭指標 //輸出引數:無 //返回值:相交返回第一個交點指標,不相交返回NULL SingleList* FirstIntersectNode(SingleList *head1,SingleList *head2) { SingleList *firstPtr = head1,*secondPtr = head2; int len1 = 0,len2 = 0; //迴圈遍歷第一個連結串列 while(firstPtr) { firstPtr = firstPtr->next; len1++; } //迴圈遍歷第二個連結串列 while(secondPtr) { secondPtr = secondPtr->next; len2++; } firstPtr = head1; secondPtr = head2; //讓指向較長連結串列的指標,先走出長出的幾步 if(len1 > len2) { for(int i=0; i < (len1-len2);i++) { firstPtr = firstPtr->next; } } else { for(int i=0; i < (len2-len1);i++) { secondPtr = secondPtr->next; } } while(firstPtr&&secondPtr) { if(firstPtr == secondPtr) { return firstPtr; } firstPtr = firstPtr->next; secondPtr = secondPtr->next; } return NULL; } |