一文讀懂連結串列反轉(迭代法和遞迴法)

你是風兒發表於2020-07-05

單向連結串列反轉的方法有很多,其中用的比較多的是迭代法和遞迴法,迭代法通俗易懂,遞迴法相對來說比較難理解一些。

最近讀了一些網上的文章對這兩種演算法的解釋後,有些自己的理解分享出來供大家參考。

單向連結串列反轉圖示:

 

一、迭代法

迭代法的解題思路是:通過迴圈遍歷的方式,使連結串列的每一個節點和它的下一個節點斷開,然後重置其下一個節點。

程式碼實現:

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ListNode {

    private int value;

    private ListNode next;


    /**
     * 迭代法
     * @param head
     * @return
     */
    public static ListNode reverseIterate(ListNode head){

        ListNode pre = null,cur = head,next = null;

        while (null != cur){
            next = cur.next;
            cur.next = pre; 
            pre = cur; 
            cur = next;
        }

        return pre;
    }

    public static void main(String[] args) {

        ListNode listNode4 = new ListNode(4, null);
        ListNode listNode3 = new ListNode(3, listNode4);
        ListNode listNode2 = new ListNode(2, listNode3);
        ListNode listNode1 = new ListNode(1, listNode2);

        System.out.println(reverseIterate(listNode1));
        
    }
}

 程式碼解釋:

對於連結串列中的一個節點(cur)來說,它包含節點的值(value)、其所指向的下一個節點(next)、及指向它的上一個節點(pre)。反轉連結串列就是要把當前節點指向的下一個節點變成指向上一個節點。(連結串列頭沒有上一個節點,連結串列尾下一個節點的指向為空)

next = cur.next; 保留當前節點的下一個節點

cur.next = pre; 重置當前節點的下一個節點為上一個節點

pre = cur; 下次遍歷需要用到的上一個節點

cur = next; 下次遍歷需要用到的當前節點

其執行過程可以用下圖表示:

 

 

迭代法的特點是:從前向後節點之間的連結依次斷開, 重置每個節點下一個節點的指向。

二、遞迴法

遞迴法的解題思路是:利用遞迴的思想和棧先進後出的特性,完成連結串列的反轉。

要理解遞迴法,首先要對遞迴和棧的特性有一定的瞭解,這裡簡單介紹一下。

遞迴:方法自己呼叫自己、遞迴需要有結束條件。

棧:先進後出。

要牢記這兩個點,對我們理解遞迴法很重要。

程式碼實現:

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ListNode {

    private int value;

    private ListNode next;

    /**
     * 遞迴法
     * @param head
     * @return
     */
    public static ListNode reverseRecursion(ListNode head){
        if (null == head || null == head.next){
            return head;
        }

        ListNode newHead = reverseRecursion(head.next);

        head.next.next = head;
        head.next = null;

        return newHead;
    }


    public static void main(String[] args) {

        ListNode listNode4 = new ListNode(4, null);
        ListNode listNode3 = new ListNode(3, listNode4);
        ListNode listNode2 = new ListNode(2, listNode3);
        ListNode listNode1 = new ListNode(1, listNode2);

        System.out.println(reverseRecursion(listNode1));

    }
}

 

程式碼解釋:

ListNode newHead = reverseRecursion(head.next);

1、遞迴法的目的是要返回一個新的頭節點,這個新的頭節點是原來連結串列的尾節點。

2、遞迴是方法自己呼叫自己,棧的特性是先進後出、後進先出。所以這段程式碼的作用是不斷的去壓棧。

head.next.next = head; 把當前節點指向的下一個節點的指向變為自己。(不要慌,這段程式碼的解釋,下面有圖有真相)

head.next = null; 當前節點指向的下一個節點變為空。

是不是看了上面的解釋還有點不理解,別急,我第一次看這段程式碼的時候也是懵逼的(內心os:這寫了個啥,怎麼就反轉連結串列了)

要理解這段程式碼首先,我們得去壓幾個棧,可能你會說我腦子裡壓不了幾個棧容量就不夠了,腦子就亂了,StackOverflowException。。。

講真,我最開始也是想放棄的,背住程式碼算了。迭代法他就不香嗎。。。

後來想到人類對圖形的理解能力要長於文字,我們把這個過程畫出來試試看能否理解。

我們還使用上文中使用過的連結串列作為資料來源來分析這個過程

ListNode newHead = reverseRecursion(head.next);這段程式碼會執行四次,壓棧四次,如圖:

  

if (null == head || null == head.next) {return head;}這段程式碼會讓 reverseRecursion(head(4)) 發生彈棧,然而連結串列沒有發生任何變化。。。方法繼續執行。
    
 
接著 reverseRecursion(head(3)) 彈棧執行下面這段程式碼

head.next.next = head;

head.next = null;

reverseRecursion(head(3)) 執行之後連結串列發生瞭如下變化:

 

     

 

下面的過程簡單了 reverseRecursion(head(2))出棧。

head.next.next = head;

head.next = null;

    

reverseRecursion(head(1))出棧:連結串列反轉完成,撒花?

head.next.next = head;

head.next = null;

 

以上就是我對單向連結串列反轉迭代法和遞迴這兩種方法的理解,希望可以幫到你。

 

 

 

 

 

 

相關文章