單連結串列的反轉困擾了我好幾天了。今天終於一通百通了,特地記錄一下,免得以後又忘記了。腦子笨,只能靠這種辦法了。
之前網上的一種做法是這樣的:
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; 這種寫法也是一個一樣的效果,但是行為是不一樣的行為。 }
如果還有不對,歡迎指正。