32

What's the best (halting) algorithm for determining if a linked list has a cycle in it?

[Edit] Analysis of asymptotic complexity for both time and space would be sweet so answers can be compared better.

[Edit] Original question was not addressing nodes with outdegree > 1, but there's some talk about it. That question is more along the lines of "Best algorithm to detect cycles in a directed graph".

eeerahul
  • 1,629
  • 4
  • 27
  • 38
cdleary
  • 69,512
  • 53
  • 163
  • 191
  • http://stackoverflow.com/questions/2663115/interview-question-how-to-detect-a-loop-in-a-linked-list – Pramod Oct 15 '12 at 09:20
  • Possible duplicate of [How to detect a loop in a linked list?](https://stackoverflow.com/questions/2663115/how-to-detect-a-loop-in-a-linked-list) – roottraveller Aug 09 '17 at 11:56

12 Answers12

51

Have two pointers iterating through the list; make one iterate through at twice the speed of the other, and compare their positions at each step. Off the top of my head, something like:

node* tortoise(begin), * hare(begin);
while(hare = hare->next)
{
    if(hare == tortoise) { throw std::logic_error("There's a cycle"); }
    hare = hare->next;
    if(hare == tortoise) { throw std::logic_error("There's a cycle"); }
    tortoise = tortoise->next;
}

O(n), which is as good as you can get.

DrPizza
  • 17,882
  • 7
  • 41
  • 53
  • 5
    Oops, you actually have a bug. The while loop should be `while (hare && hare = hare->next) otherwise you could iterate right off the end. – 1800 INFORMATION Sep 18 '08 at 20:21
  • My favorite algorithm ever!! I can't wait to get it on an interview. – TonyOssa Sep 19 '08 at 21:48
  • 2
    You don't need while(hare && hare = hare->next). The only time that would protect you is if the list was empty (root is null). Otherwise, the while loop as defined will terminate as soon as hare->next returns null. – Herms Sep 26 '08 at 15:15
  • 10
    @Herms, hare is set to hare->next within the loop body so it's possible for hare to be null at this point. – Wedge Oct 09 '08 at 18:12
  • 2
    There's a very detailed treatment of this subject in Elements of Programming by Stepanov & McJones. Chapter 2, which you can read here, http://www.elementsofprogramming.com/book.html covers this. – Jackson Aug 18 '09 at 13:30
  • Actually, you are making _three_ passes through the list in the worst case, and _two_ when there is no loop. Vertex coloring would require one pass to reset the color, and one more pass in all cases. So while it would use slightly more memory per node (just a bit, which could be even accommodated through bit stealing), it would be faster than your algorithm when there _is_ a cycle, doing one less pass. If we are talking about a big cycle in a list, this can be significant. – Dimitris Andreou Jul 17 '10 at 01:53
  • 18
    By the way, "off the top of your head" might imply to someone that you invented this algorithm, misleading people referring to it as "DrPizza's algorithm" (!). Lets give credit where it is due. This is published by Floyd as early as 1967: http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare – Dimitris Andreou Jul 17 '10 at 01:59
  • 2
    I mean "off the top of my head" in the sense of "writing directly into the text box without reference to a compiler or anything else". However, you're wrong; Floyd's algorithm locates cycles. The question here is only to ascertain that there is a cycle. The principle behind both algorithms is the same, but this is substantially simpler. – DrPizza Jul 17 '10 at 03:20
  • @DrPizza your algorithm seems fail to detect this case 1-2-3-4-5-6-2,the node contains data 6 links to the second node in the sequence. – Tracy Oct 21 '10 at 12:20
  • 1
    Uhg, throwing an exception is not a reasonable behavior for a predicate function (one that is supposed to answer a question "Does object X have property P?") – R.. GitHub STOP HELPING ICE May 29 '11 at 18:25
  • 1
    @DrPizza, I don't see that hare is twice as fast as tortoise. Am I missing anything? – Alcott Sep 13 '11 at 02:58
  • @Alcott - yes, `hare = hare->next` happens twice in the loop. Do you need another clue? :) – Kieren Johnstone Nov 24 '11 at 19:52
  • @R..: I disagree; this is no mere predicate function, this is a test of logical validity. If a linked list has a loop, the program has a logic error and should crash immediately, since program state is invalid and untrustworthy. Throwing an exception is, if anything, not severe enough. – DrPizza Nov 27 '11 at 06:21
  • 1
    @DrPizza: If that's the case, I agree, but I don't see any indication in OP's question that this test is about program consistency and it's wrong to use exceptions as an ingredient in solving the general problem. – R.. GitHub STOP HELPING ICE Nov 27 '11 at 06:58
  • Shouldn't `node* tortoise(begin), hare(begin);` be `node* tortoise(begin), * hare(begin);` ? The tight binding of asterisk will cause an error. Correct me if I am wrong. – Sumit Gera Dec 26 '13 at 08:28
  • @SumitGera It should indeed. – DrPizza Aug 03 '15 at 06:05
2

Precondition: Keep track of the list size (update the size whenenver a node is added or deleted).

Loop detection:

  1. Keep a counter when traversing the list size.

  2. If the counter exceeds the list size, there is a possible cycle.

Complexity: O(n)

Note: the comparison between the counter and the list size, as well as the update operation of the list size, must be made thread-safe.

kbsant
  • 136
  • 3
1

Take 2 pointer *p and *q , start traversing the linked list "LL" using both pointers :

1) pointer p will delete previous node each time and pointing to next node.

2) pointer q will go each time in forward direction direction only.

conditions:

1) pointer p is pointing to null and q is pointing to some node : Loop is present

2) both pointer pointing to null: no loop is there

mac jc
  • 21
  • 2
0

You will have to visit every node to determine this. This can be done recursively. To stop you visiting already visited nodes, you need a flag to say 'already visited'. This of course doesn't give you loops. So instead of a bit flag, use a number. Start at 1. Check connected nodes and then mark these as 2 and recurse until the network is covered. If when checking nodes you encounter a node which is more than one less than the current node, then you have a cycle. The cycle length is given by the difference.

Guillermo Phillips
  • 2,176
  • 1
  • 23
  • 40
0

Two pointers are initialized at the head of list. One pointer forwards once at each step, and the other forwards twice at each step. If the faster pointer meets the slower one again, there is a loop in the list. Otherwise there is no loop if the faster one reaches the end of list.

The sample code below is implemented according to this solution. The faster pointer is pFast, and the slower one is pSlow.

bool HasLoop(ListNode* pHead)
{
    if(pHead == NULL)
        return false;


    ListNode* pSlow = pHead->m_pNext;
    if(pSlow == NULL)
        return false;


    ListNode* pFast = pSlow->m_pNext;
    while(pFast != NULL && pSlow != NULL)
    {
        if(pFast == pSlow)
            return true;


        pSlow = pSlow->m_pNext;


        pFast = pFast->m_pNext;
        if(pFast != NULL)
            pFast = pFast->m_pNext;
    }


    return false;
}

This solution is available on my blog. There is a more problem discussed in the blog: What is the entry node when there is cycle/loop in a list?

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Harry He
  • 1,795
  • 16
  • 12
0

"Hack" solution (should work in C/C++):

  • Traverse the list, and set the last bit of next pointer to 1.
  • If find an element with flagged pointer -- return true and the first element of the cycle.
  • Before returning, reset pointers back, though i believe dereferencing will work even with flagged pointers.

Time complexity is 2n. Looks like it doesn't use any temporal variables.

Ayrat
  • 1,221
  • 1
  • 18
  • 36
0

What about using a hash table to store the already seen nodes (you look at them in order from the start of the list)? In practise, you could achieve something close to O(N).

Otherwise, using a sorted heap instead of a hash table would achieve O(N log(N)).

OysterD
  • 6,660
  • 5
  • 34
  • 33
0

I wonder if there's any other way than just going iteratively - populate an array as you step forwards, and check if the current node is already present in the array...

Henrik Paul
  • 66,919
  • 31
  • 85
  • 96
0

Konrad Rudolph's algorithm won't work if the cycle isn't pointing to the beginning. The following list will make it an infinite loop: 1->2->3->2.

DrPizza's algorithm is definitely the way to go.

Liedman
  • 10,099
  • 4
  • 34
  • 36
0

In this case OysterD's code will be the fastest solution (vertex coloring)

That would really surprise me. My solution makes at most two passes through the list (if the last node is linked to the penultimate lode), and in the common case (no loop) will make only one pass. With no hashing, no memory allocation, etc.

DrPizza
  • 17,882
  • 7
  • 41
  • 53
0

In this case OysterD's code will be the fastest solution (vertex coloring)

That would really surprise me. My solution makes at most two passes through the list (if the last node is linked to the penultimate lode), and in the common case (no loop) will make only one pass. With no hashing, no memory allocation, etc.

Yes. I've noticed that the formulation wasn't perfect and have rephrased it. I still believe that a clever hashing might perform faster – by a hair. I believe your algorithm is the best solution, though.

Just to underline my point: the vertec coloring is used to detect cycles in dependencies by modern garbage collectors so there is a very real use case for it. They mostly use bit flags to perform the coloring.

Community
  • 1
  • 1
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
0

This is a solution using a Hash table ( just a list actually) to save the pointer address.

def hash_cycle(node):
    hashl=[]
    while(node):
        if node in hashl:
            return True
        else:
            hashl.append(node)
            node=node.next
    return False
FlyingAura
  • 1,541
  • 5
  • 26
  • 41