如何判斷單向鏈表有環?

前言:鏈表在開發過程中屬於出現頻次十分高的一種數據結構,在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,本羣旨在打造無培訓廣告、無閒聊扯淡、無注水鬥圖的純技術交流羣,羣裏每天會分享有價值的問題和學習資料,歡迎各位隨時加入 

 

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