views:

2379

answers:

16

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 - i.e. 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? (should be O(n) time)

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

alt text

+5  A: 

Take a look at Pollard's rho algorithm. It's not quite the same problem, but maybe you'll understand the logic from it, and apply it for linked lists.

(if you're lazy, you can just check out cycle detection -- check the part about the tortoise and hare.)

This only requires linear time, and 2 extra pointers.

In Java:

boolean hasLoop( Node first ) {
    if ( first == null ) return false;

    Node turtle = first;
    Node hare = first;

    while ( hare.next != null && hare.next.next != null ) {
         turtle = turtle.next;
         hare = hare.next.next;

         if ( turtle == hare ) return true;
    }

    return false;
}

(Most of the solution do not check for both next and next.next for nulls. Also, since the turtle is always behind, you don't have to check it for null -- the hare did that already.)

Larry
+3  A: 

The following may not be the best method--it is O(n^2). However, it should serve to get the job done (eventually).

count_of_elements_so_far = 0;
for (each element in linked list)
{
    search for current element in first <count_of_elements_so_far>
    if found, then you have a loop
    else,count_of_elements_so_far++;
}
Sparky
+8  A: 

you should somehow mark nodes walked over
if next node has the mark - you have found a loop

oraz
Agree. If this was an actual interview question or homework assignment given to me, I'd put a marker into the Node class and do a trivial implementation of hasLoop(). Only if I'm explicitly not allowed to change the node implementation I'd bother with the more advanced cycle-finding algorithms.
Christoffer
But then its not a constant amount of space. you're adding O(n) memory requirement.
Ofri Raviv
i'll mark walked over node by destroing it's next value (assign for example to 0x1); if i'll found node with destoyed next value => there was a loop in this queue :)
oraz
"Destroying" the next value is not correct, as it has to be an object of type Node (0x1 wont work) and it cannot be null (which stands for the end of the list).Create one global, list-unrelated instance of Node and call it 'visited'. Then traverse the list and for every visited Node, do "this.next=visited;". If you ever encounter a node with next==visited (yes, "=="), you have a loop. And the 'added' memory requirement is exactly 1 object.
f1sh
But how would you initialize this marker each time the method is called?
Dave L.
A: 

Let firstnode be given.

f1 = firstnode;
f2 = firstnode.next;
while (f1 != f2 && f1.next!=null && f2.next!=null){
 f1 = f1.next;
 f2 = f2.next.next;
}
if (f1 == f2 && f1!=null && f2!=null) return "LOOP";
return "No loop";
phimuemue
f2.next.next == null
Timmy
How is this even remotely correct? f1 and f2 start off `==`. So the first loops fails immediately. Then the if statement evaluates to true as long as firstnode is not null. So it will return "LOOP". Right?
Tom
Yep, you're right. I think I was a bit too fast there.
phimuemue
@Henk - So you say firstnode is always equal to firstnode.next?P.S. ok probably the code is edited :).
sahs
Also doesn't handle firstnode being null.
Dave L.
+71  A: 

You can make use of Floyd's cycle-finding algorithm, also know 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;
    }
}
codaddict
Okay, but at the moment you have fast just hopping once - doesn't it need to do fast = fast.next.next?
jjujuma
Oops..thanx for pointing :)
codaddict
Also need to do a null-check on `fast.next` before calling `next` again: `if(fast.next!=null)fast=fast.next.next;`
cmptrgeekken
@cmptrgeekken: Thnx for pointing :)
codaddict
you should check not only (slow==fast) but: (slow==fast || slow.next==fast) to prevent jumping the fast over the slow
oraz
@oraz: Even if they jump they will finally meet again later. But yes we can do this check as an optimization.
codaddict
i was wrong: fast can't jump over slow, because to jump over slow on next step fast should has the same pos as slow :)
oraz
The check for slow == null is redundant unless the list has just one node. You can also get rid of one call to Node.next. Here's a simpler and faster version of the loop: http://pastie.org/927591
Kay Sarraute
You should really cite your references. This algorithm was invented by Robert Floyd in the '60s, It's known as Floyd's cycle-finding algorithm, aka. The Tortoise and Hare Algorithm.
joshperry
This code won't work for odd length lists. If `fast.next == null` then the element `fast` refers to is never advanced. After some time, the `slow` reference will catch up and falsely report a loop.
Tim Bender
Tim is right, don't use this solution as is! Scary that such a broken solution would get voted so high.
Dave L.
@Tim: Thanks for pointing. I've updated my answer.
codaddict
+22  A: 

Turtle and Rabbit algorithm will solve it.

Hun1Ahpu
A: 

I cannot see any way of making this take a fixed amount of time or space, both will increase with the size of the list.

I would make use of an IdentityHashMap (given that there is not yet an IdentityHashSet) and store each Node into the map. Before a node is stored you would call containsKey on it. If the Node already exists you have a cycle.

ItentityHashMap uses == instead of .equals so that you are checking where the object is in memory rather than if it has the same contents.

TofuBeer
It's certainly impossible for it to take a fixed amount of time, as there could be a loop at the very end of the list, so the entire list must be visited. However, the Fast/Slow algorithm demonstrates a solution using a fixed amount of memory.
Dave L.
+12  A: 

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);
}
meriton
+1 great idea..
oraz
+1 faster than turtle and rabbit and more cache friendly
Peter G.
+1  A: 
public boolean hasLoop(Node start){   
   TreeSet<Node> set = new TreeSet<Node>();
   Node lookingAt = start;

   while (lookingAt.peek() != null){
       lookingAt = lookingAt.next;

       if (set.contains(lookingAt){
           return false;
        } else {
        set.put(lookingAt);
        }

        return true;
}   
// Inside our Node class:        
public Node peek(){
   return this.next;
}

Forgive me my ignorance (I'm still fairly new to Java and programming), but why wouldn't the above work?

I guess this doesn't solve the constant space issue... but it does at least get there in a reasonable time, correct? It will only take the space of the linked list plus the space of a set with n elements (where n is the number of elements in the linked list, or the number of elements until it reaches a loop). And for time, worst-case analysis, I think, would suggest O(nlog(n)). SortedSet look-ups for contains() are log(n) (check the javadoc, but I'm pretty sure TreeSet's underlying structure is TreeMap, whose in turn is a red-black tree), and in the worst case (no loops, or loop at very end), it will have to do n look-ups.

smessing
Yes a solution with some kind of Set works fine, but requires space proportional to the size of the list.
jjujuma
+1  A: 

If we're allowed to embed the class Node, I would solve the problem as I've implemented it below. hasLoop() runs in O(n) time, and takes only the space of counter. Does this seem like an appropriate solution? Or is there a way to do it without embedding Node? (Obviously, in a real implementation there would be more methods, like RemoveNode(Node n), etc.)

public class LinkedNodeList {
    Node first;
    Int count;

    LinkedNodeList(){
        first = null;
        count = 0;
    }

    LinkedNodeList(Node n){
        if (n.next != null){
            throw new error("must start with single node!");
        } else {
            first = n;
            count = 1;
        }
    }

    public void addNode(Node n){
        Node lookingAt = first;

        while(lookingAt.next != null){
            lookingAt = lookingAt.next;
        }

        lookingAt.next = n;
        count++;
    }

    public boolean hasLoop(){

        int counter = 0;
        Node lookingAt = first;

        while(lookingAt.next != null){
            counter++;
            if (count < counter){
                return false;
            } else {
               lookingAt = lookingAt.next;
            }
        }

        return true;

    }



    private class Node{
        Node next;
        ....
    }

}
smessing
+3  A: 

Brent's Cycle Detection Algorithm

gameover
+7  A: 

The user unicornaddict has a nice algorithm above, but unfortunately it contains a bug for non-loopy lists of odd length >= 3. The problem is that fast can get "stuck" just before the end of the list, slow catches up to it, and a loop is (wrongly) detected.

Here's the corrected algorithm.

static 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 = null;
        else
            fast = fast.next.next; // 2 hops.

        if(fast == null) // if fast hits null..no loop.
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop.
            return true;
    }
}
Carl Mäsak
+1  A: 

You can also look the Nivasch's algorithm here: Nivasch's algorithm.

Or you can check Gabriel Nivasch's personal homepage at The stack algorithm for cycle detection which also contains a C implementation of the algorithm.

+2  A: 

Here's a refinement of the Fast/Slow solution, which correctly handles odd length lists and improves clarity.

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
}
Dave L.
A: 

i think it can be done in one of the simplest way by O(n) complexity.

as you traverse the list starting from head, create a sorted list of adresses. when you insert a new adress, just check it is already there in the sorted list. the sorting is of O(logN) complexity only :-)

abhinav