常見鏈表操作-單鏈表反轉(JAVA實現)

在技術面試中,單鏈表的操作經常會被問到,比如一些常見的問題:

  • 單鏈表反轉
  • 鏈表中環的檢測
  • 兩個有序表的合併
  • 刪除鏈表倒數第n個節點
  • 求鏈表的中間節點

接下來的文章,我對這些操作的實現算法做了一些總結,具體實現的編程語言是Java。

今天第一篇,先講講如何實現單鏈表反轉。

實現思路

第一步,從頭結點開始遍歷鏈表,找到尾結點。

第二步,從尾結點開始,反向修改每個節點的next引用,直到頭結點。

第三步,修改頭結點的next引用爲null。

實現代碼

定義節點結構。

/**
 * 鏈表ADT
 * 
 * @author wangtao
 * @param <T>
 */
public class LinkADT<T> {

    /**
     * 單鏈表節點
     * 
     * @author wangtao
     * @param <T>
     */
    private static class SingleNode<T> {
        public SingleNode<T> next;
        public T data;

        public SingleNode(T data) {
            this.data = data;
        }

        public T getNextNodeData() {
            return next != null ? next.data : null;
        }
    }
}

第一種實現方案,使用遞歸思路,易懂但是不夠簡潔。

    /**
     * 單鏈表反轉 version1
     * 
     * @param node
     *            當前節點
     * @param pre
     *            前一個節點
     * @return
     */
    public static SingleNode reverseV1(SingleNode node, SingleNode pre) {

        if (node == null) {
            return node;
        } else {
            // 反轉後的頭結點
            SingleNode headNode;

            // next爲空,說明是尾節點
            if (node.next == null) {
                // 修改next引用
                node.next = pre;
                // 指定反轉後的頭節點
                headNode = node;
            } else {
                // 非尾節點,繼續遞歸
                headNode = reverseV1(node.next, node);
                //
                node.next = pre;
            }

            return headNode;
        }
    }

驗證結果。

    /**
     * 打印鏈表信息
     * 
     * @param node
     */
    public static void printLink(SingleNode node) {
        System.out.println("current node data:" + node.data + ", next node data:" + node.getNextNodeData());

        if (node.next != null) {
            printLink(node.next);
        } else {
            System.out.println("-------------");
        }
    }

    public static void main(String[] args) {
        SingleNode<Integer> s1 = new SingleNode<>(1);
        SingleNode<Integer> s2 = new SingleNode<>(2);
        SingleNode<Integer> s3 = new SingleNode<>(3);
        SingleNode<Integer> s4 = new SingleNode<>(4);
        s1.next = s2;
        s2.next = s3;
        s3.next = s4;
        
        printLink(s1);
        SingleNode<Integer> firstNode = reverseV1(s1, null);
        printLink(firstNode);
    }

打印結果,鏈表被正確反轉。

current node data:1, next node data:2
current node data:2, next node data:3
current node data:3, next node data:4
current node data:4, next node data:null
-------------
current node data:4, next node data:3
current node data:3, next node data:2
current node data:2, next node data:1
current node data:1, next node data:null
-------------

爲什麼說前面的方案不夠簡潔呢?因爲參數中有當前節點的前一個節點,例如下面的代碼

    SingleNode<Integer> firstNode = reverseV1(s1, null);

下面來看看更加簡潔的第二種實現方案。

    /**
     * 單鏈表反轉 version2
     * 
     * @param node
     * @return
     */
    public static SingleNode reverseV2(SingleNode node) {
        if (node == null || node.next == null) {
            return node;
        } else {
            SingleNode headNode = reverseV2(node.next);
            node.next.next = node;
            node.next = null;
            return headNode;
        }
    }

重點是下面這行代碼,將下一個節點的next引用修改爲當前節點。
乍一看以爲有bug,仔細分析之後發現沒問題,如果想不明白,建議跟一次斷點。

node.next.next = node; 

總結

單鏈表反轉看上去簡單,但是在沒有準備的情況想要寫對也不容易。如果肯花時間,抽一兩個小時來寫一寫代碼,相信大家都能掌握。

完整代碼請參考:
https://github.com/wanf425/Algorithm/blob/master/src/com/wt/adt/LinkADT.java

下一篇文章,我會繼續總結如何實現鏈表中環的檢測

博文地址

https://www.taowong.com/blog/2018/10/14/data-strutctures-and-algorithm-01.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章