關於單連結串列反轉的一點整理

lukely發表於2019-08-13

單連結串列的反轉困擾了我好幾天了。今天終於一通百通了,特地記錄一下,免得以後又忘記了。腦子笨,只能靠這種辦法了。

之前網上的一種做法是這樣的:

 1 public void reversList(){
 2             Node pre = null;
 3             Node next = null;
 4             while (head != null) {
 5                 next = head.next;
 6                 head.next = pre;
 7                 pre = head;
 8                 head = next; 
 9             }
10                 head = pre;
11 }

 

 核心的程式碼就是這一段:

但實際上做種做法是錯誤的。我們先看看反轉之後的情況。

一遍一遍遍歷的過程是這樣的。每一行就是一次遍歷。

1 Node [data=4, next=Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]]
2 Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]
3 Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]
4 Node [data=1, next=Node [data=null, next=null]]
5 Node [data=null, next=null]

 

這個實際上我沒辦法遍歷,我是用最笨的辦法一點一點還原出來的,如下:

1 System.out.println(list.getHead());
2 System.out.println(list.getHead().next);
3 System.out.println(list.getHead().next.next);
4 System.out.println(list.getHead().next.next.next);
5 System.out.println(list.getHead().next.next.next.next);

 

我們再看看連結串列在反轉之前是什麼樣子的,同樣適用一樣的笨辦法:

1 Node [data=null, next=Node [data=1, next=Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]]]
2 Node [data=1, next=Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]]
3 Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]
4 Node [data=3, next=Node [data=4, next=null]]
5 Node [data=4, next=null]

現在看來,反轉前後,連結串列中有效的值只是大概相等而已,實際上並不一致。其在首尾節點上也是不一樣的。

這種思路一開始是從head開始遍歷開始反轉的,但是其實head是一個指標節點,真正有效的節點是head.next,應該從這個開始遍歷替換,所以真正應該遍歷的當前節點其實是 head.next。而應該先儲存的當前節點的下一個節點其實是 head.next.next。這是第一個錯誤。

第二個錯誤是:用來儲存前一個節點的變數,不應該是Node  pre = null;而應該是Node pre = new Node(null)。這兩種寫法,完全是兩個不同的東西。Node  pre = null 完全就是隻有一個棧空間裡的引用而已,根本沒有指向堆裡的空間,事實上這就不是一個物件。而Node pre = new Node(null)這種寫法,是一種實打實的物件,是真正的引用指向了堆裡面的空間了。所以當第二步  head.next = pre;(正確的是:head.next.next = pre.next)的時候,得到的結果就完全不一樣了。如果pre=null;那就是把前一個有效節點都置為null了,這個物件就死了,真正要做的是斷開前一個節點與後一個節點之間的指標連線,所以要置為null的是 pre.next,pre的指標域是null,而不是pre本身是null。這是兩碼事。這裡我第一次看到網上的答案時,還不明白,為什麼不可以直接直接Node  pre = null,這下才算是明白。

第一步第二步都錯了之後,後面的步驟肯定也是錯的了。

那為什麼最後還可以得出一個對的假象呢?即:

Node [data=4, next=Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]]

最後得到的連結串列是這個,

這就是因為第二次迴圈之後,pre的next域,被恰好填充了物件,但是這個物件卻是一個錯誤的物件如:

Node [data=1, next=Node [data=null, next=null]]

具體怎麼錯,以後慢慢研究吧。

正確的思路應該是,從head的next域指向的第一個有效節點開始遍歷。可以直接在原來的連結串列上進行遍歷替換。最後用反轉之後的連結串列的第一個有效節點再接上head.next,這樣head的指標域就指向了反轉之後的連結串列。如下:

 1  public void reversList(){
 2          Node pre = new Node(null);
 3         //Node pre = null;錯誤寫法
 4             Node next = null;
 5          
 6             while (head.next != null) {//從第一個有效節點開始遍歷
 7                 next = head.next.next;//先記錄當前節點的下一個節點
 8                 head.next.next = pre.next;//將當前節點的指標域置空
 9                 pre.next = head.next;//將下一節點的指標域,指向當前節點,完成節點交換
10                 head.next = next;//往後移動
11             }
12            
13                 head.next = pre.next;//最後把反轉後的節點與頭結點聯絡上。最終得到完整的反轉之後的連結串列。
14 }

 

或者也。可以新建立一個新的連結串列,然後遍歷老連結串列後,把節點新增到新連結串列的第一個有效節點位置。最後返回新的連結串列,或者用新的連結串列覆蓋head。也許這樣比較容易理解:

public void reversList(){
             Node temp = head.next;//設定一個臨時變數儲存第一個有效節點
             Node newHead = new Node(null);//設定一個新的頭結點
             Node next = null;//用於儲存下一個節點
             
             while(temp != null){
                 next = temp.next;//儲存當前節點的下一個節點
                 temp.next = newHead.next;//將當前節點的指標域置空
                 newHead.next = temp;//始終把當前節點插入到新連結串列的第一個有效節點的位置
                 temp = next;//向後移動一個節點
             }
            
             head = newHead;//最後把新的連結串列的頭節點賦值給原節點的頭結點,完成兩個連結串列的合併
              //   head.next = newHead.next; 這種寫法也是一個一樣的效果,但是行為是不一樣的行為。
        }        

 如果還有不對,歡迎指正。

相關文章