How to detect a loop in a linked list?

Say you have a linked list structure in Java. It's made up of Nodes:

class Node {
    Node next;
    // some user data
}

and each Node points to the next node, except for the last Node, which has null for next. Say there is a possibility that the list can contain a loop - ie the final Node, instead of having a null, has a reference to one of the nodes in the list which came before it.

What's the best way of writing

boolean hasLoop(Node first)

which would return true if the given Node is the first of a list with a loop, and false otherwise? How could you write so that it takes a constant amount of space and a reasonable amount of time?

Here's a picture of what a list with a loop looks like:


You can make use of Floyd's cycle-finding algorithm , also known as tortoise and hare algorithm.

The idea is to have two references to the list and move them at different speeds . Move one forward by 1 node and the other by 2 nodes.

  • If the linked list has a loop they will definitely meet.
  • Else either of the two references(or their next ) will become null .
  • Java function implementing the algorithm:

    boolean hasLoop(Node first) {
    
        if(first == null) // list does not exist..so no loop either
            return false;
    
        Node slow, fast; // create two references.
    
        slow = fast = first; // make both refer to the start of the list
    
        while(true) {
    
            slow = slow.next;          // 1 hop
    
            if(fast.next != null)
                fast = fast.next.next; // 2 hops
            else
                return false;          // next node null => no loop
    
            if(slow == null || fast == null) // if either hits null..no loop
                return false;
    
            if(slow == fast) // if the two ever meet...we must have a loop
                return true;
        }
    }
    

    下面是Fast / Slow解决方案的改进,它可以正确处理奇数长度的列表并提高清晰度。

    boolean hasLoop(Node first) {
        Node slow = first;
        Node fast = first;
    
        while(fast != null && fast.next != null) {
            slow = slow.next;          // 1 hop
            fast = fast.next.next;     // 2 hops 
    
            if(slow == fast)  // fast caught up to slow, so there is a loop
                return true;
        }
        return false;  // fast reached null, so the list terminates
    }
    

    An alternative solution to the Turtle and Rabbit, not quite as nice, as I temporarily change the list:

    The idea is to walk the list, and reverse it as you go. Then, when you first reach a node that has already been visited, its next pointer will point "backwards", causing the iteration to proceed towards first again, where it terminates.

    Node prev = null;
    Node cur = first;
    while (cur != null) {
        Node next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    boolean hasCycle = prev == first && first != null && first.next != null;
    
    // reconstruct the list
    cur = prev;
    prev = null;
    while (cur != null) {
        Node next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    
    return hasCycle;
    

    Test code:

    static void assertSameOrder(Node[] nodes) {
        for (int i = 0; i < nodes.length - 1; i++) {
            assert nodes[i].next == nodes[i + 1];
        }
    }
    
    public static void main(String[] args) {
        Node[] nodes = new Node[100];
        for (int i = 0; i < nodes.length; i++) {
            nodes[i] = new Node();
        }
        for (int i = 0; i < nodes.length - 1; i++) {
            nodes[i].next = nodes[i + 1];
        }
        Node first = nodes[0];
        Node max = nodes[nodes.length - 1];
    
        max.next = null;
        assert !hasCycle(first);
        assertSameOrder(nodes);
        max.next = first;
        assert hasCycle(first);
        assertSameOrder(nodes);
        max.next = max;
        assert hasCycle(first);
        assertSameOrder(nodes);
        max.next = nodes[50];
        assert hasCycle(first);
        assertSameOrder(nodes);
    }
    
    链接地址: http://www.djcxy.com/p/39682.html

    上一篇: 为什么QuickSort是n log n的直观解释?

    下一篇: 如何检测链表中的循环?