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
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
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())
ok - I ran into this in an interview yesterday - no reference material available and I came up with a very different answer (while driving home of course...) Since the linked lists are NORMALLY (not always I admit) allocated using malloc logic then we know that the granularity of the allocations is known. On most systems this is 8 bytes - this means that the bottom 3 bits are always zeros. Consider - if we place the linked list in a class to control access and use a mask of 0x0E ored into the next address then we can use the lower 3 bits to store a break crumb Thus we can write a method that will store our last breadcrumb - say 1 or 2 - and alternate them. Our method that checks for a loop can then step through each node (using our next method) and check if the next address contains the current breadcrumb - if it does we have a loop - if it does not then we would mask the lower 3 bits and add our current breadcrumb. The breadcrumb checking algorithm would have to be single threaded as you could not run two of them at once but it would allow other threads to access the list async - with the usual caveats about adding/deleting nodes. What do you think? If others feel this is a valid solution I can write up the sample class ... Just think sometimes a fresh approach is good and am always willing to be told I have just missed the point... Thanks All Mark
bool FindLoop(struct node *head)
{
struct node *current1,*current2;
current1=head;
current2=head;
while(current1!=NULL && current2!= NULL && current2->next!= NULL)
{
current1=current1->next;
current2=current2->next->next;
if(current1==current2)
{
return true;
}
}
return false;
}
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.
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:
head
is null
). If so, no cycle exists, so stop now.tortoise
on the first node head
, and the second pointer hare
on the second node head.next
.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.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:
hare
and set size
to 1
.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.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.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.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:
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
Another solution
Detecting a Loop:
Removal of loop:
Once we detect the loop in Step#3, set previous node's next value to NULL
#code
def detect_remove_loop(head)
cur_node = head
node_list = []
while cur_node.next is not None:
prev_node = cur_node
cur_node = cur_node.next
if cur_node not in node_list:
node_list.append(cur_node)
else:
print('Loop Detected')
prev_node.next = None
return
print('No Loop detected')