Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding loop in a singly linked-list

Tags:

linked-list

How can I detect that whether a singly linked-list has loop or not?? If it has loop then how to find the point of origination of the loop i.e. the node from which the loop has started.

like image 496
Jainendra Avatar asked Apr 23 '12 06:04

Jainendra


People also ask

How do you find a loop in a linked list?

Step 1: Move 'S' to the start of the list, but 'F' would remain point to node 3. Step 2: Move 'S' and 'F' forward one node at a time until they meet. Step 3: The node where they meet is the start of the loop. Let's visualize the above algorithm for more clarity.

How do you find a loop node in a linked list?

Write a function findFirstLoopNode() that checks whether a given Linked List contains a loop. If the loop is present then it returns point to the first node of the loop. Else it returns NULL.

How do you iterate through a singly linked list?

An Iterator can be used to loop through an LinkedList. The method hasNext( ) returns true if there are more elements in LinkedList and false otherwise. The method next( ) returns the next element in the LinkedList and throws the exception NoSuchElementException if there is no next element.

What is the best way to detect a cycle in a linked list?

A simple solution is to use hashing. The idea is to traverse the given list and insert each encountered node into a set. If the current node already presents in the set (i.e., it is seen before), that means a cycle is present in the list.


6 Answers

You can detect it by simply running two pointers through the list, this process is known as the tortoise and hare algorithm after the fable of the same name:

  • First off, check if the list is empty (head is null). If so, no cycle exists, so stop now.
  • Otherwise, start the first pointer tortoise on the first node head, and the second pointer hare on the second node head.next.
  • Then loop continuously until hare is null (which may be already true in a one-element list), advancing tortoise by one and hare by two in each iteration. The hare is guaranteed to reach the end first (if there is an end) since it started ahead and runs faster.
  • If there is no end (i.e., if there is a cycle), they will eventually point to the same node and you can stop, knowing you have found a node somewhere within the cycle.

Consider the following loop which starts at 3:

head -> 1 -> 2 -> 3 -> 4 -> 5
                  ^         |
                  |         V
                  8 <- 7 <- 6

Starting tortoise at 1 and hare at 2, they take on the following values:

(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6)

Because they become equal at (6,6), and since hare should always be beyond tortoise in a non-looping list, it means you've discovered a cycle.

The pseudo-code will go something like this:

def hasLoop (head):
  return false if head = null           # Empty list has no loop.

  tortoise = head                       # tortoise initially first element.
  hare = tortoise.next                  # Set hare to second element.

  while hare != null:                   # Go until hare reaches end.
    return false if hare.next = null    # Check enough left for hare move.
    hare = hare.next.next               # Move hare forward two.

    tortoise = tortoise.next            # Move tortoise forward one.

    return true if hare = tortoise      # Same means loop found.
  endwhile

  return false                          # Loop exit means no loop.
enddef

The time complexity for this algorithm is O(n) since the number of nodes visited (by tortoise and hare) is proportional to the number of nodes.


Once you know a node within the loop, there's also an O(n) guaranteed method to find the start of the loop.

Let's return to the original position after you've found an element somewhere in the loop but you're not sure where the start of the loop is.

head -> 1 -> 2 -> 3 -> 4 -> 5
                  ^         |
                  |         V
                  8 <- 7 <- 6
                             \
                              x (where hare and tortoise met).

This is the process to follow:

  • Advance hare and set size to 1.
  • Then, as long as hare and tortoise are different, continue to advance hare, increasing size each time. This eventually gives the size of the cycle, six in this case.
  • At this point, if size is 1, that means you must already be at the start of the cycle (in a cycle of size one, there is only one possible node that can be in the cycle so it must be the first one). In this case, you simply return hare as the start, and skip the rest of the steps below.
  • Otherwise, set both hare and tortoise to the first element of the list and advance hare exactly size times (to the 7 in this case). This gives two pointers that are different by exactly the size of the cycle.
  • Then, as long as hare and tortoise are different, advance them both together (with the hare running at a more sedate pace, the same speed as the tortoise - I guess it's tired from its first run). Since they will remain exactly size elements apart from each other at all times, tortoise will reach the start of the cycle at exactly the same time as hare returns to the start of the cycle.

You can see that with the following walkthrough:

size  tortoise  hare  comment
----  --------  ----  -------
   6         1     1  initial state
                   7  advance hare by six
             2     8  1/7 different, so advance both together
             3     3  2/8 different, so advance both together
                      3/3 same, so exit loop

Hence 3 is the start point of the cycle and, since both those operations (the cycle detection and cycle start discovery) are O(n) and performed sequentially, the whole thing taken together is also O(n).


If you want a more formal proof that this works, you can examine the following resources:

  • a question on our sister site;
  • the Wikipedia cycle detection page; or
  • "The Tortoise and the Hare Algorithm" by Peter Gammie, April 17, 2016.

If you're simply after support for the method (not formal proof), you can run the following Python 3 program which evaluates its workability for a large number of sizes (how many elements in the cycle) and lead-ins (elements before the cycle start).

You'll find it always finds a point where the two pointers meet:

def nextp(p, ld, sz):
    if p == ld + sz:
        return ld
    return p + 1

for size in range(1,1001):
    for lead in range(1001):
        p1 = 0
        p2 = 0
        while True:
            p1 = nextp(p1, lead, size)
            p2 = nextp(nextp(p2, lead, size), lead, size)
            if p1 == p2:
                print("sz = %d, ld = %d, found = %d" % (size, lead, p1))
                break
like image 162
paxdiablo Avatar answered Oct 11 '22 22:10

paxdiablo


The selected answer gives an O(n*n) solution to find the start node of the cycle. Here's an O(n) solution:

Once we find the slow A and fast B meet in the cycle, make one of them still and the other continue to go one step each time, to decide the perimeter of the cycle, say, P.

Then we put a node at the head and let it go P steps, and put another node at the head. We advance these two nodes both one step each time, when they first meet, it's the start point of the cycle.

like image 42
Dongliang Yu Avatar answered Oct 11 '22 22:10

Dongliang Yu


You can use hash map also to finding whether a link list have loop or not below function uses hash map to find out whether link list have loop or not

    static bool isListHaveALoopUsingHashMap(Link *headLink) {

        map<Link*, int> tempMap;
        Link * temp;
        temp = headLink;
        while (temp->next != NULL) {
            if (tempMap.find(temp) == tempMap.end()) {
                tempMap[temp] = 1;
            } else {
                return 0;
            }
            temp = temp->next;
        }
        return 1;
    }

Two pointer method is best approach because time complexity is O(n) Hash Map required addition O(n) space complexity.

like image 28
Gyaneshwar Pardhi Avatar answered Oct 11 '22 22:10

Gyaneshwar Pardhi


I read this answer in Data structure book by Narasimha Karamanchi.

We can use Floyd cycle finding algorithm, also known as tortoise and hare algorithm. In this, two pointers are used; one (say slowPtr) is advanced by a single node, and another (say fastPtr) is advanced by two nodes. If any loop is present in the single linked list, they both will surely meet at some point.

struct Node{
int data;
struct Node *next;

}

 // program to find the begin of the loop

 int detectLoopandFindBegin(struct Node *head){
      struct Node *slowPtr = head, *fastPtr = head;
      int loopExists = 0;
      // this  while loop will find if  there exists a loop or not.
      while(slowPtr && fastPtr && fastPtr->next){                                                  
        slowPtr = slowPtr->next;                      
        fastPtr = fastPtr->next->next;
        if(slowPtr == fastPtr)
        loopExists = 1;
        break;
      }

If there exists any loop then we point one of the pointers to the head and now advance both of them by single node. The node at which they will meet will be the start node of the loop in the single linked list.

        if(loopExists){      
             slowPtr = head;
             while(slowPtr != fastPtr){
               fastPtr = fastPtr->next;
               slowPtr = slowPtr->next;
             }
             return slowPtr;
          }
         return NULL;
        }
like image 37
Ayush Chaurasia Avatar answered Oct 11 '22 23:10

Ayush Chaurasia


For the most part all the previous answers are correct but here is a simplified version of the logic with visual & code (for Python 3.7)

The logic is very simple as others explained it. I'm gonna create Tortoise/slow and Hare/fast. If we move two pointers with different speed then eventually fast will meet the slow !! you can also think of this as two runners in a tack circular field. If the fast runner keeps going in circle then it will meet/pass the slow runner.

So, we will move Tortoise/slow pointer with speed 1 for each iteration while we keep incrementing or move the Hare/fast pointer with speed of 2. Once they meet we know there is a cycle. This is also known as Floyd's cycle-finding algorithm enter image description here

Here is the Python code that does this (notice has_cycle method is the main part):

#!/usr/bin/env python3
class Node:
    def __init__(self, data = None):
        self.data = data
        self.next = None
    def strnode (self):
        print(self.data)


class LinkedList:
    def __init__(self):
        self.numnodes = 0
        self.head = None


    def insertLast(self, data):
        newnode = Node(data)
        newnode.next = None
        if self.head == None:
            self.head = newnode
            return

        lnode = self.head
        while lnode.next != None :
            lnode = lnode.next
        lnode.next = newnode # new node is now the last node
        self.numnodes += 1

    def has_cycle(self):    
        slow, fast = self.head ,self.head  
        while fast != None:       
            if fast.next != None:
                 fast = fast.next.next
            else:
                 return False
            slow = slow.next  
            if slow == fast:
                print("--slow",slow.data, "fast",fast.data) 
                return True    
        return False


linkedList = LinkedList()
linkedList.insertLast("1")
linkedList.insertLast("2")
linkedList.insertLast("3")


# Create a loop for testing 
linkedList.head.next.next.next = linkedList.head; 
#let's check and see !
print(linkedList.has_cycle())
like image 45
grepit Avatar answered Oct 12 '22 00:10

grepit


Following code will find whether there is a loop in SLL and if there, will return then starting node.

int find_loop(Node *head){

    Node * slow = head;
    Node * fast =  head;
    Node * ptr1;
    Node * ptr2;
    int k =1, loop_found =0, i;

    if(!head) return -1;

    while(slow && fast && fast->next){
            slow = slow->next;
        /*Moving fast pointer two steps at a time */
            fast = fast->next->next;
            if(slow == fast){
                    loop_found = 1;
                    break;
            }

    }

    if(loop_found){
    /* We have detected a loop */
    /*Let's count the number of nodes in this loop node */

            ptr1  = fast;
            while(ptr1 && ptr1->next != slow){
                    ptr1 = ptr1->next;
                    k++;
            }
    /* Now move the other pointer by K nodes */
            ptr2 = head;

            ptr1  = head;
            for(i=0; i<k; i++){
                    ptr2 = ptr2->next;
            }

    /* Now if we move ptr1 and ptr2 with same speed they will meet at start of loop */

            while(ptr1 != ptr2){
                    ptr1  = ptr1->next;
                    ptr2 =  ptr2->next;
            }

    return ptr1->data;

}
like image 24
jitsceait Avatar answered Oct 11 '22 23:10

jitsceait