带环单链表查找环的入口算法(Java语言描述)

带环单链表的故事

@不了解前尘往事的Reader,烦请阅读——《判断单链表是否有环的算法》

如何找带环单链表的环的入口

这里只说比较可行的算法吧。

思路一:HashSet第一个重复元素就是环的入口

按照查找单链表带环的思路二,我们用一个HashSet维护已经跑过的元素,当重复的时候,那个结点就是环的入口。这法子还算好使,不过还是老问题——空间复杂度大。

思路二:再开一个指针与当前指针相会

我们当前双指针停在交汇处,这里有一个位置。

思来想去我还是给大家画个图吧:
在这里插入图片描述

有两个画错的地方:就是其实l1应该由node4画到node7,再就是node6上的线应该把焦点改到node7。

大家也读过上面的文章应该也知道我的测试数据是:
node1 → node2 → node3 → node4 → node5 → node6 → node7 → node8 → node9 → node4
环入口在node4处。
我们按照快慢双指针的思路走一遍判断环的流程:
t = 0:prev = node1,rear = node1;
t = 1:prev = node3,rear = node2;
t = 2:prev = node5,rear = node3;
t = 3:prev = node7,rear = node4;
t = 4:prev = node9,rear = node5;
t = 5:prev = node5,rear = node6;
t = 6:prev = node7,rear = node7。
(画错了,其实应该是在node7相遇的……不过不影响推理)

显然,第一轮的慢指针必然跑不完一圈。

我们做一些定义:
初始点(node1)到环入口点(node4)为l0
环入口点(node4)到交汇点(node7)为l1
交汇点(node7)到环入口点(node4)为l2
环入口点(node4)到环入口点(node4)一圈为r;
慢指针跑过的结点路程为s,快指针跑过的路程是2s且在环内(环入口点(node4)顺到环入口点(node4)为环内一圈)跑了n圈。

由于慢指针跑不完一圈,所以说:s = l0 + l1(1)
由于快指针跑了n圈圈内,多出来的部分正好是l1,所以说:2s = l0 + l1 + nr(2)
(2)式 -(1)式,得到:s = nr(3)
由于一圈长度为r,也是l1 + l2,所以说:r = l1 + l2(4)
由(4)式得:l1 = r - l2(5)
(5)式带入(1)式得:s = l0 + l1 = l0 + r - l2(6)
由(3)和(6)得,s = nr = l0 + r - l2(7)
(7)移项得:l0 = (n-1)r + l2(8)
(8)式即为所求。

(8)式告诉我们什么呢?
排一个新的指针new从node1出发,另外从prev和rear中抽一个指针出来与new同时出发,每次都移动一个结点,最终必定在环入口点(图中node4)处相遇,相遇的一瞬间,那个点就是环的入口。

这就是完整的算法推导过程。其实网上有很多写的乱七八糟的,看不懂怎么推的。我就自己推了一下,结合着实例给大家讲解到这里,还望满意。

Java编程实现

首先对之前的isCircular()进行了修改,把返回值从boolean改成了Node,便于调用。

private Node<T> isCircular() {
    Node<T> prev = first;
    Node<T> rear = first;
    while (prev.next != null) {
        prev = prev.next;
        if (prev.next == null) {
            return null;
        }
        prev = prev.next;
        rear = rear.next;
        if (prev == rear) {
            return prev;
        }
    }
    return null;
}

接下来是新的方法getEntry()了:

private Node<T> getEntry() {
    Node<T> prev = isCircular();
    if (prev == null) {
        return null;
    }
    Node<T> rear = first;
    while (prev != rear) {
        prev = prev.next;
        rear = rear.next;
    }
    return prev;
}

完整代码(Java语言描述)

public class Main {

    private static class Node<T> {
        T element;
        Node<T> next;
        Node(T element) {
            this.element = element;
        }
    }

    private static class LinkedList<T> {
        Node<T> first;
        private Node<T> isCircular() {
            Node<T> prev = first;
            Node<T> rear = first;
            while (prev.next != null) {
                prev = prev.next;
                if (prev.next == null) {
                    return null;
                }
                prev = prev.next;
                rear = rear.next;
                if (prev == rear) {
                    return prev;
                }
            }
            return null;
        }
        private Node<T> getEntry() {
            Node<T> prev = isCircular();
            if (prev == null) {
                return null;
            }
            Node<T> rear = first;
            while (prev != rear) {
                prev = prev.next;
                rear = rear.next;
            }
            return prev;
        }
    }

    public static void main(String[] args) {
        Node<Integer> node1 = new Node<>(1);
        Node<Integer> node2 = new Node<>(2);
        Node<Integer> node3 = new Node<>(3);
        Node<Integer> node4 = new Node<>(4);
        Node<Integer> node5 = new Node<>(5);
        Node<Integer> node6 = new Node<>(6);
        Node<Integer> node7 = new Node<>(7);
        Node<Integer> node8 = new Node<>(8);
        Node<Integer> node9 = new Node<>(9);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node8;
        node8.next = node9;
        node9.next = node4;
        LinkedList<Integer> list = new LinkedList<>();
        list.first = node1;
        System.out.println("链表是否有环:" + (list.isCircular() == null ? "false" : "true"));
        Node<Integer> entry = list.getEntry();
        System.out.println("链表环的入口是:" + (entry == null ? "不存在" : "Node"+entry.element));
    }

}
发布了538 篇原创文章 · 获赞 1098 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/weixin_43896318/article/details/104384771