對單鏈表進行反轉是一個很基本的算法。下面將介紹3種不一樣的單鏈表反轉操做,須要注意的是,咱們所討論的單鏈表是包含頭節點的。算法
咱們的鏈表節點和main函數以及部分函數的代碼以下:函數
1 #include <cstdio> 2 3 struct LNode { 4 int data; 5 LNode *next; 6 }; 7 8 LNode* read(); 9 void print(LNode *L); 10 11 int main() { 12 LNode *L1, *L2; 13 L1 = read(); 14 L2 = reverse3(L1); 15 print(L2); 16 17 return 0; 18 } 19 20 LNode* read() { 21 LNode *head = new LNode; // 申請一個鏈表節點作爲頭節點 22 head -> next = NULL; 23 LNode *last = head; // last指針用於指向最後一個節點,咱們經過尾插法進行插入 24 25 int n; 26 scanf("%d", &n); 27 while (n--) { 28 LNode *p = new LNode; 29 scanf("%d", &p -> data); 30 p -> next = NULL; 31 last = last -> next = p; 32 } 33 34 return head; 35 } 36 37 void print(LNode *L) { 38 L = L -> next; // 由於鏈表帶有頭節點,咱們須要在頭節點的下一個節點纔開始遍歷輸出 39 while (L) { 40 printf("%d ", L -> data); 41 L = L -> next; 42 } 43 putchar('\n'); 44 }
1 LNode* reverse(LNode *L) { 2 LNode *preNode = NULL, *curNode = L -> next; 3 while (curNode) { 4 LNode *nextNode = curNode -> next; 5 curNode -> next = preNode; 6 preNode = curNode; 7 curNode = nextNode; 8 } 9 L -> next = preNode; 10 11 return L; 12 }
須要說明的是curNode指向的是當前須要反轉的節點。spa
preNode指向的是當前節點的上一個節點,也就是curNode所指向的節點的上一個節點。3d
而nextNode指向的是下一個節點,也就是curNode所指向的節點的下一個節點,由於當前節點指向上一個後,該節點本來存有的後續節點的地址就丟失了,因此咱們須要nextNode來存放後續節點的地址。指針
如今咱們先建立一個存放着4個數據的鏈表。而後,第一次進入循環,執行完上述第4行代碼後,有以下圖:code
重複上面的步驟,在第二次的循環結束後,變化爲下圖:blog
繼續重複,當咱們走完了循環後,整個鏈表就會變爲下面這個樣子:遞歸
咱們發現頭節點並無指向存放着數據4的這個節點,因此退出了循環後,就要讓頭節點指向存放着數據4的這個節點,而此時preNode正指向它,因此咱們能夠直接執行L -> next = preNode; 也就是執行第9行這個語句。以後,鏈表就完成了反轉,變成下面這個樣子,而後返回頭指針L,就經過迭代這個方法完成了整一個鏈表反轉的操做。it
1 LNode* reverse(LNode *L) { 2 LNode *curNode = L -> next; 3 L -> next = NULL; 4 while (curNode) { 5 LNode *nextNode = curNode -> next; 6 curNode -> next = L -> next; 7 L -> next = curNode; 8 curNode = nextNode; 9 } 10 11 return L; 12 }
curNode指向的是當前須要反轉的節點。io
一樣的,咱們須要一個nextNode來存放後續節點的地址。
與上述的迭代反轉不一樣,咱們是把當前的節點插入到頭節點以後,也就是經過頭插法把每個節點插到頭節點以後。
同樣,如今咱們先建立一個存放着4個數據的鏈表。在進入循環以前,咱們先要讓頭節點指向NULL,不過在此以前,須要用curNode來保存頭節點所指向的節點的地址,也就是第1個存放數據的節點的地址。
而後,第一次進入循環,執行完上述第4行代碼,有以下圖:
重複上面的步驟,在第二次的循環結束後,變化爲下圖:
繼續重複,當咱們走完了循環後,整個鏈表就會變爲下面這個樣子:
循環結束後,咱們的鏈表反轉操做也結束了,接下來只須要返回頭指針L就能夠了。
1 LNode* reverseFrom2Node(LNode *L) { 2 L -> next = reverse(L -> next); 3 return L; 4 } 5 6 LNode* reverse(LNode *L) { 7 if (L == NULL || L -> next == NULL) { 8 return L; 9 } 10 LNode *last = reverse(L -> next); 11 L -> next -> next = L; 12 L -> next = NULL; 13 14 return last; 15 }
看不懂?先不要急!
先補充說明,在上述代碼中,reverse函數能夠對不帶頭節點的鏈表進行反轉操做,也就是說,若是鏈表不帶有頭節點,只須要經過reverse函數就能夠完成整一個鏈表的反轉。可是,因爲咱們的鏈表帶有頭節點,若是隻調用reverse函數就行不通了。因此咱們還須要另外一個函數reverseFrom2Node(正如函數名同樣,從第二個節點開始反轉)。
爲了方便說明,咱們先假設鏈表不帶有頭節點,先來看看reverse函數是如何工做的。
對於這個遞歸算法,咱們首先要明確這個遞歸的定義。
就是,咱們傳入一個頭指針L,而後將以頭指針爲起點的鏈表進行反轉,並返回反轉後的鏈表的第一個節點的地址。而當鏈表的長度爲1,也就是隻有一個節點時,因爲反轉後仍是其自身,因此返回的依然是原來傳入的那個頭指針。注意,此時咱們的鏈表不帶有頭節點。接下來,咱們對下面這個鏈表執行reverse函數,進行遞歸反轉。
第一次進入reverse函數後,因爲L -> next != NULL,因此不會進入到選擇語句中。而後咱們執行第10行,進行第一次遞歸。
不要跳進遞歸裏面去!而是要根據剛纔的遞歸函數定義,來弄清楚這行代碼會產生什麼結果:
第10行代碼執行完後,整個鏈表就會變成這樣子,根據定義,reverse返回的是反轉後鏈表的第一個節點的地址,咱們用last來接收,如圖:
接下來是第11行的代碼,目的是讓上圖的第二個節點(從左到右數)指向第一個節點(從左到右數),執行完後以下:
第12行代碼就是讓L指向的那個節點,也就是第一個節點指向NULL。
以後咱們return last,就將整一個鏈表進行反轉,並返回反轉後的鏈表的頭節點。
OK,如今你應該理解了reverse函數是如何工做的了。接下來解釋reverseFrom2Node這個函數。
在解釋reverse函數中,咱們的鏈表是不帶有頭節點的。如今,咱們的鏈表又帶有頭節點了。
你可能會問,若是咱們讓帶頭節點的鏈表直接執行reverse這個函數,會產生什麼樣的結果。正如咱們前面所說的,reverse函數是將鏈表的第一個節點到最後一個節點進行反轉,並無說能夠只反轉其中一個部分。若是讓帶頭節點的鏈表執行reverse函數,就會變成下面這樣子:
很明顯,這不是咱們想要的結果。
咱們實際上是想讓頭節點以後的剩下節點進行反轉,而後再將頭節點指向last所指向的這個節點,也就是這樣子:
爲了達到這個目的,其實咱們只要給reverse函數傳入L -> next,不就能夠了嗎。也就是說,咱們只反轉除頭節點以外的剩下部分的節點,而不反轉頭節點。reverse調用結束後(注意,並非執行完第2行的代碼後),會返回反轉後鏈表的第一個節點的地址,(也就是上圖對應的last所指向的節點,只是在咱們的代碼種並無last這個中間變量,而是將reverse返回的值直接賦值給L -> next),如圖:
此時,咱們只須要L -> next = last,就能夠完成整一個鏈表的反轉了。
在上面的代買中咱們直接讓L -> next來接收reverse返回的值,達到一樣的效果。
因此,咱們才定義了reverseFrom2Node這個函數。
綜上,咱們的遞歸反轉是分兩部進行的。因爲咱們的鏈表帶有頭節點,因此須要先讓除頭節點以外剩餘節點進行反轉,也就是執行reverse(L -> next); 而後再讓頭節點來接收reverse函數返回的反轉後的鏈表的第一個節點的地址,從而完成了整一個鏈表的反轉。
到這裏,咱們已經解釋完遞歸反轉是如何實現的了。
其實,還有一種算法是隻對鏈表中的第n個到第m個節點進行反轉,上述只是該算法的一種特殊狀況,也就是讓鏈表中第2個節點到最後一個節點進行反轉。若是要實現鏈表中的第n個到第m個節點反轉,相關的代碼就徹底不是咱們上面的那個樣子了。
關於這個算法,能夠參考:如何遞歸反轉鏈表 —— https://zhuanlan.zhihu.com/p/86745433
就此,關於帶有頭節點的單鏈表反轉操做的3種方法已經介紹完了,下面再附上包含這3種反轉方法的完整代碼:
1 #include <cstdio> 2 3 struct LNode { 4 int data; 5 LNode *next; 6 }; 7 8 LNode* read(); 9 void print(LNode *L); 10 LNode* reverse1(LNode *L); 11 LNode* reverse2(LNode *L); 12 LNode* reverseFrom2Node(LNode *L); 13 LNode* reverse3(LNode *L); 14 15 int main() { 16 LNode *L1, *L2; 17 L1 = read(); 18 19 // 反轉的3種方法 20 // L2 = reverse1(L1); 21 // L2 = reverse2(L1); 22 // L2 = reverseFrom2Node(L1); 23 24 print(L2); 25 26 return 0; 27 } 28 29 LNode* read() { 30 LNode *head = new LNode; 31 head -> next = NULL; 32 LNode *last = head; 33 34 int n; 35 scanf("%d", &n); 36 while (n--) { 37 LNode *p = new LNode; 38 scanf("%d", &p -> data); 39 p -> next = NULL; 40 last = last -> next = p; 41 } 42 43 return head; 44 } 45 46 void print(LNode *L) { 47 L = L -> next; 48 while (L) { 49 printf("%d ", L -> data); 50 L = L -> next; 51 } 52 putchar('\n'); 53 } 54 55 // 迭代反轉鏈表 56 LNode* reverse1(LNode *L) { 57 LNode *preNode = NULL, *curNode = L -> next; 58 while (curNode) { 59 LNode *nextNode = curNode -> next; 60 curNode -> next = preNode; 61 preNode = curNode; 62 curNode = nextNode; 63 } 64 L -> next = preNode; 65 66 return L; 67 } 68 69 // 就地逆置法反轉鏈表 70 LNode* reverse2(LNode *L) { 71 LNode *curNode = L -> next; 72 L -> next = NULL; 73 while (curNode) { 74 LNode *nextNode = curNode -> next; 75 curNode -> next = L -> next; 76 L -> next = curNode; 77 curNode = nextNode; 78 } 79 80 return L; 81 } 82 83 // 遞歸反轉鏈表 84 LNode* reverseFrom2Node(LNode *L) { 85 L -> next = reverse3(L -> next); 86 return L; 87 } 88 89 LNode* reverse3(LNode *L) { 90 if (L == NULL || L -> next == NULL) { 91 return L; 92 } 93 LNode *last = reverse3(L -> next); 94 L -> next -> next = L; 95 L -> next = NULL; 96 97 return last; 98 }
感謝你的閱讀!
如何遞歸反轉鏈表:https://zhuanlan.zhihu.com/p/86745433