1. 程式人生 > >Java實現有環的單向連結串列,並判斷單向連結串列是否有環

Java實現有環的單向連結串列,並判斷單向連結串列是否有環

有一個單向連結串列,連結串列當中有可能出現環,就像下圖這樣。我們如何判斷一個單向連結串列是否有環呢?
有環的單向連結串列

那麼第一步,我們先實現一個這樣的連結串列,接著再說如何判斷這樣的連結串列。

實現有環的單向連結串列

1、定義add(Node node)方法

    /**
     * 向連結串列末尾新增結點
     *
     * @param node 結點的next指向為null,表示尾結點。
     * @return
     */
    public boolean add(SingleNode node) {
        if (first == null) {
            first = node;
        } else {
            SingleNode last = get(getSize() - 1);
            last.next = node;
        }
        size++;
        return true;
    }

    /**
     * 末尾新增一個特殊結點,形成一個環,為了測試用。
     *
     * @param node 這個node的next指向單項鍊表中已經存在的結點。
     * @return
     */
    public boolean addCycleNode(SingleNode node) {
        if (getSize() < 2) {
            return false;
        }
        SingleNode last = get(getSize() - 1);
        last.next = node;
        return true;
    }

2、先正常生成一個無環的單向連結串列,最後在末尾新增一個結點,讓該結點的next指向前面的某個結點。

        // 迴圈連結串列
        SingleLinkedList sll = new SingleLinkedList();
        SingleNode head = new SingleNode(0, null);
        sll.add(head);
        for (int i = 1; i < 10; i++) {
            SingleNode node = new SingleNode(i, null);
            sll.add(node);
        }
        SingleNode sn = new SingleNode(111, null);
        sll.add(sn);
        for (int i = 10; i < 20; i++) {
            SingleNode node = new SingleNode(i, null);
            sll.add(node);
        }
        System.out.println(sll.toString());

輸出如下:此時列印的還是一個無環單向連結串列。

SingleLinkedList:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 111, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

接著新增最後一個結點,讓其next指向之前的sn結點,這樣就形成了一個閉環。

        // 最後一個結點的next指向之前新增的某個結點。
        SingleNode last = new SingleNode(222, sn);
        sll.add(last);

接著列印結果:需要注意的是,這裡的列印log的方法我做了處理,具體細節請檢視原始碼。

cycle:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 111, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 222, 111]

最後插入的結點的值為222,它的next指向的是111結點,如上所示,在222之後列印的就是111節點了,如果繼續列印,就會一直迴圈列印下去。

判斷單向連結串列是否有環

方法一:使用Hash Table
判斷一個連結串列是否有環,我們只需要判斷某個結點是否之前被訪問過即可。這裡我們優先想到的就是使用Hash表進行儲存。
我們依次遍歷訪問每個結點,並將結點的引用儲存到hash表中。如果當前結點是null,說明我們到達連結串列未尾部了,則當前連結串列一定無環;如果當前結點在hash表中已經儲存過了,此時返回true即可。程式碼如下:

    /**
     * 判斷單鏈表中是否有環
     * 藉助HashSet來判斷。
     *
     * @param head
     * @return
     */
    public boolean hasCycleByHashSet(SingleNode head) {
        Set<SingleNode> set = new HashSet<>();
        SingleNode node = head;
        while (node != null) {
            if (set.contains(node)) {
                return true;
            } else {
                set.add(node);
            }
            node = node.next;
        }
        return false;
    }

方法二:使用快慢指標
首先建立兩個指標1和2(在Java裡就是兩個物件引用),同時指向這個連結串列的頭結點。然後開始一個大迴圈,在迴圈體中,1每次移動一個結點,2每次移動2個結點。最後比較這兩個結點指向的結點是否相同。如果相同,則判斷出連結串列有環,如果不同,則繼續下一次迴圈。

程式碼如下:

    /**
     * 判斷單鏈表中是否有環
     * 藉助快慢指標來判斷。
     *
     * @param head
     * @return
     */
    public boolean hasCycleByTowPointers(SingleNode head) {
        // 排除無資料或者只有一個數據且無閉環的情況
        if (head == null || head.next == null){
            return false;
        }
        SingleNode slow =  head;
        SingleNode fast = head.next;
        while(slow != fast){
            // 判斷快指標結點是否為null,如果為null,則說明到達單向連結串列的結尾了。
            if(fast == null || fast.next == null){
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }

完整程式碼請檢視

專案中搜索SingleLinkedList即可。
github傳送門 https://github.com/tinyvampirepudge/DataStructureDemo

gitee傳送門 https://gitee.com/tinytongtong/DataStructureDemo

參考:
1、linked-list-cycle

2、漫畫演算法:如何判斷連結串列有環?

3、使用Java實現單向連結串列,並完成連結串列反轉。