1. 程式人生 > >如何判斷單向連結串列有環?

如何判斷單向連結串列有環?

前言:連結串列在開發過程中屬於出現頻次十分高的一種資料結構,在java中,比如我們熟知的LinkedList、HashMap底層結構、LinkedHashMap、AQS等都使用到了連結串列,關於單向連結串列有幾個經典問題 1:如何判斷連結串列有環  2:如果有環,找出入環的節點 3:環的長度是多少?本篇部落格就圍繞這三個問題來展開討論

目錄

一:如何判斷單向連結串列有環?

二:如果有環,找出入環的節點

三:環的長度是多少?

四:測試

五:總結

問題一:如何判斷單向連結串列有環?

首先我們來畫一個普通的單向連結串列和環狀連結串列的結構圖:

 

 

可以看出在環形單向連結串列的EFGH形成了一個環狀,那麼如何用程式判斷它成環呢?這裡要藉助一個跑道的思想:假如有一個環形的跑道,跑道上有兩個人P和Q,假設P的速度是1km/10分鐘,Q的速度是2km/10分鐘,速度恆定不變。如果這個跑道是環型的,他們同時出發,起初Q領先,而在某一個時刻,Q終將從後面追上過P,他們兩一定會相遇,而如果是直線跑道,P和Q一定不會相遇。藉助於這個思想,我們可以設定快慢指標去繞著環狀連結串列去走,如果兩個指標相遇,那麼它肯定是環形的。

下面是java版的實現:通過設定兩個不同速度的快慢指標來遍歷整個連結串列,如果快慢相遇,則整個連結串列一定有環:

 

 

 

程式的具體實現:

   /**
     * 連結串列節點
     */
    public static class Node {
        private String value;
        private Node next;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public Node getNext() {
            return next;
        }

        public void setNext(Node next) {
            this.next = next;
        }
    } 
     /**
     * 連結串列是否有環
     * @param sourceNode
     * @return
     */
    public static boolean hasCircle(Node sourceNode) {
        if (sourceNode == null) {
            return false;
        }
        if (sourceNode.next == null) {
            return false;
        }
        //慢指標
        Node slowPointer = sourceNode;
        //快指標
        Node fastPointer = sourceNode;
        while (fastPointer != null) {

            //慢指標每次一個連結串列格
            slowPointer = slowPointer.next;
            //快指標每次兩個連結串列格
            fastPointer = fastPointer.next.next;
            if (slowPointer == fastPointer) {
                return true;
            }
        }
        return false;
    }

 

問題二:如果有環,找出入環的節點

   假設環形連結串列的長度是L,相遇點在M,在相遇之後,只需要將fast指標指向開始的節點,然後和slow指標保持同一的速度遍歷(相當於此時不分快慢,每個指標的每次步長為1),下一次兩個節點相遇的時候就是連結串列的環形入口:關於此結論的數學證明:

   https://zhuanlan.zhihu.com/p/33663488

 

 

程式實現如下:

 /**
     * 獲取入口節點
     * @param sourceNode
     * @return
     */
    public static Node getEnterNode(Node sourceNode) {

        if (sourceNode == null) {
            return null;
        }
        if (sourceNode.next == null) {
            return null;
        }
        //慢指標
        Node slowPointer = sourceNode;
        //快指標
        Node fastPointer = sourceNode;
        while (fastPointer != null) {
            slowPointer = slowPointer.next;
            fastPointer = fastPointer.next.next;
            if (slowPointer == fastPointer) {
                break;
            }
        }
        System.out.println("相遇點"+fastPointer.getValue());
        fastPointer = sourceNode;
        while (fastPointer != null) {
            fastPointer = fastPointer.next;
            slowPointer = slowPointer.next;
            if (fastPointer == slowPointer) {
                return fastPointer;
            }
        }
        return null;
    }

 

問題三:環的長度是多少?

   這個問題比較簡單,既然我們已經知道了環的入口節點,只需要新增一個指標,順著環依次迴圈一遍用一個變數進行累加,每次的步長設為一,然後直到和入口節點相遇(環入口的節點位置保持不變)那麼環的長度也就統計出來了:

 

  程式具體實現:

 /**
     * 獲取環的長度
     *
     * @param sourceNode
     * @return
     */
    public static int getCirCleLength(Node sourceNode) {
        if (sourceNode == null) {
            return 0;
        }
        final Node enterNode = getEnterNode(sourceNode);
        //環的下一個指標
        Node cirCleSecondNode = enterNode.next;
        int lenght = 1;
        while (cirCleSecondNode != enterNode) {
            lenght++;
            cirCleSecondNode = cirCleSecondNode.next;
        }
        return lenght;
    }

 

 四:測試

我們來寫一個測試方法來模擬一下上面的環狀節點,然後測試一下:

public static void main(String[] args) {

        final Node node = new Node();
        node.setValue("A");
        final Node node2 = new Node();
        node2.setValue("B");
        final Node node3 = new Node();
        node3.setValue("C");
        final Node node4 = new Node();
        node4.setValue("D");
        final Node node5 = new Node();
        node5.setValue("E");
        final Node node6 = new Node();
        node6.setValue("F");
        final Node node7 = new Node();
        node7.setValue("G");
        final Node node8 = new Node();
        node8.setValue("H");
        node.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node4);
        node4.setNext(node5);
        node5.setNext(node6);
        node6.setNext(node7);
        node7.setNext(node8);
        node8.setNext(node5);
        final boolean hasCircle = hasCircle(node);
        System.out.println("是否是環形連結串列:"+hasCircle);

        final Node enterNode = getEnterNode(node);
        System.out.println("相遇節點是:"+enterNode.getValue());

        final int cirCleLength = getCirCleLength(node);
        System.out.println("環狀長度:"+cirCleLength);
        
    }

 

程式輸出如下:

 

 五:總結

  本次主要分析了環形連結串列的一些問題,並給出了示例程式碼,通過此篇部落格可以學習到關於連結串列的一些東西,快慢指標的基本思想,以及如何求相遇節點和環的長度兩個問題,如何用java求解,並熟悉連結串列這種資料結構,在實際工作中可以加深對環形連結串列的一些理解。

 

最後: 如果對學習java有興趣可以加入群:618626589,本群旨在打造無培訓廣告、無閒聊扯淡、無注水鬥圖的純技術交流群,群裡每天會分享有價值的問題和學習資料,歡迎各位隨時加入