圖解帶頭節點的單鏈表的反轉操做

前言

  對單鏈表進行反轉是一個很基本的算法。下面將介紹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

相關文章
相關標籤/搜索