圖解:單鏈表反轉的三種方式


當我們在聊到連結串列反轉的時候,一定說的都是單鏈表,雙鏈表本身就具有前驅指標 Prev 和後續指標 next,無需進行翻轉。
單鏈表反轉,反轉後的效果如下:

看起來很簡單,只需要將單鏈表所有結點的 next 指向,指向它的前驅節點即可。引入一個棧結構,就可以實現。
棧實現的連結串列反轉
在原本連結串列的資料結構之外,引入一個棧( 陣列也可 ),將單鏈表迴圈遍歷,將所有結點入棧,最後再從棧中迴圈出棧,記住出棧的順序,得到的就是反轉後的單鏈表。

但是這樣實現,有一個問題,它會額外消耗一個棧的記憶體空間,此時空間複雜度就變成了 O(n)。並且,棧會遇到的問題,使用此種方式都會遇到,例如比較常見的,棧的深度問題。
空間複雜度為 O(1) 單鏈表反轉
接下來我們看看如何解決空間複雜度的問題。
在排序演算法中,有一個概念叫 原地排序,指的是不需要引入額外的儲存空間,在原資料結構的基礎上進行排序 。這種排序演算法的空間複雜度是 O(1)。例如我們常見的氣泡排序、插入排序都是原地排序演算法。
這裡,我們也可以在原單鏈表的資料結構上,進行單鏈表反轉。
原地單鏈表反轉,是一種很基礎的演算法,但是有一些在面試中遇到這道題,思路不清晰時,一時半會也寫不出來。
容易出錯的點在於, 指標丟失 。在轉換結點指標的時候,所需結點和指標反轉順序,都很重要,一不小心,就會丟掉原本的後續指標 next,導致連結串列斷裂。

在上一篇文章中,帶單鏈表時間複雜度為 O(1) 的結點刪除法中,介紹到,刪除單鏈表的時候,需要知道前後三個結點。在單鏈表翻轉的時候,也是這樣。
當我們要對一個結點進行指標翻轉的時候,我們也需要知道三個結點。
-
待翻轉的結點。
-
待反轉結點的前驅結點 prev。
-
待反轉結點的後續結點 next。
說了那麼多,直接上程式碼。
static class Node { int data; Node next; Node(int data){ this.data = data; } } static Node reverseByLoop(Node head) { if (head == null || head.next == null){ return head; } Node preNode = null; Node nextNode = null; while (head != null){ nextNode = head.next; head.next = preNode; preNode = head; head = nextNode; } return preNode; }
連結串列翻轉的所有邏輯,都在 reverseByLoop()
方法中,此處以頭結點為引數,反轉之後返回值為反轉後的單鏈表頭結點。
建議最好自己在 IDE 裡敲一遍,加深印象。
遞迴實現單鏈表反轉
單鏈表反轉,還可以通過遞迴來實現,但是這裡不推薦使用,大家瞭解一下就好了。
遞迴還是在藉助函式呼叫棧的思想,其實本質上也是一個棧。沒什麼好說的,直接上程式碼。
static Node reverseByRecursion(Node head){ if(head == null || head.next == null){ return head; } Node newHead = reverseByRecursion(head.next); head.next.next = head; head.next = null; return newHead; }
小結時刻
到這裡,單鏈表反轉的內容,都介紹完了,學演算法還是要考慮多寫多練,推薦大家在 IDE 中,自己手動敲一遍,加深印象。
本文對你有幫助嗎? 留言、點贊、轉發 是最大的支援,謝謝!
「聯機圓桌」:point_left:推薦我的知識星球,一年 50 個優質問題,上桌聯機學習。
公眾號後臺回覆成長『 成長 』,將會得到我準備的學習資料,也能回覆『 加群 』,一起學習進步;你還能回覆『 提問 』,向我發起提問。
推薦閱讀:
關於字元編碼,你需要知道的都在這裡 |圖解:HTTP 範圍請求 |Java 異常處理 | 安卓防止使用者關閉動畫導致動畫失效 |Git 找回遺失的程式碼 | 阿里的 Alpha 助力 App 啟動速度優化
