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