5 Best Ways to Detect a Loop in a Linked List Using Python

πŸ’‘ Problem Formulation: Detecting a loop within a linked list is a common challenge in computer science. A loop occurs when a node’s next pointer points to a previous node in the sequence, creating a cycle that can cause algorithms to enter an infinite loop. The goal is to write a program in Python that identifies the presence of a loop in a linked list. If a loop exists, our program should be able to indicate that a loop is present as the output.

Method 1: Floyd’s Cycle-Finding Algorithm

This algorithm uses two pointers at different speeds to traverse the linked list. The slow pointer moves one node at a time, while the fast pointer moves two nodes at a time. If there is a loop, the fast pointer will eventually lap the slow pointer. Function specification: Given the head of a linked list, determine if the list has a cycle in it.

Here’s an example:

class ListNode:
    def __init__(self, value=0, next=None):
        self.value = value
        self.next = next

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

# Example usage:
# Create a cycle list: 1->2->3->4->2 (cycle starts at node with value 2)
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node2
print(has_cycle(node1))

Output: True

The function has_cycle uses two pointers to traverse the list in a controlled manner. If both pointers meet at the same node, it means there is a loop in the linked list. The provided code snippet creates a linked list with a loop and then uses our function to detect the cycle, which returns True.

Method 2: Hash Table Approach

The hash table approach uses a data structure to keep track of visited nodes. As we traverse the linked list, each node is checked against the hash table. If a node re-appears, a loop is detected. Function specification: Traverse the linked list and use a hash table to store and check for previously visited nodes.

Here’s an example:

def has_cycle_with_hashing(head):
    visited = set()
    current = head
    while current:
        if current in visited:
            return True
        visited.add(current)
        current = current.next
    return False

# Same example usage as Method 1
print(has_cycle_with_hashing(node1))

Output: True

In this code snippet, the has_cycle_with_hashing function checks if each node has been visited by adding each traversed node to a set. Revisiting a node indicates the presence of a cycle, returning True.

Method 3: Brent’s Algorithm

Brent’s Algorithm is similar to Floyd’s algorithm but is often more efficient. It moves two pointers around the loop with different speeds and typically uses fewer iterations to find the cycle. Function specification: Detect the presence of a cycle using two pointers, distinguishing by updating the movement rate periodically.

Here’s an example:

def has_cycle_brent(head):
    power = lam = 1
    tortoise = head
    hare = head.next
    while tortoise != hare:
        if hare is None or hare.next is None:
            return False
        if power == lam:
            tortoise = hare
            power *= 2
            lam = 0
        hare = hare.next
        lam += 1
    return True

# Same example usage as Method 1
print(has_cycle_brent(node1))

Output: True

The has_cycle_brent function detects a loop by increasing the speed of the ‘hare’ pointer exponentially. Once the hare and tortoise meet at the same node, it confirms the presence of a loop, which is indicated by returning True. This method typically uses fewer steps than Floyd’s cycle-finding algorithm.

Method 4: Reverse List Approach

The reverse list approach takes an interesting twist: it reverses the linked list. If the list has a loop, the process of reversing will end up at the starting node again. Function specification: Reverse the linked list and check if the head node can be reached again.

Here’s an example:

def reverse_list(head):
    prev = None
    current = head
    while current:
        next_node =  current.next
        current.next = prev
        prev = current
        current = next_node
    return prev

def has_cycle_reverse(head):
    original_head = head
    reversed_head = reverse_list(head)
    return original_head == reversed_head

# Same example usage as Method 1
print(has_cycle_reverse(node1))

Output: False

This snippet defines two functions: reverse_list and has_cycle_reverse. While this method may conceptually detect a cycle by comparing the start and end after reversal, it doesn’t work correctly in practice for cyclic lists because the reversal never completes. Thus, the output is incorrectly False.

Bonus One-Liner Method 5: Set Membership in Recursion

A recursive function can be designed to check for a loop by passing a visited node set in each recursive call. Function specification: Recursively traverse the linked list while maintaining a set of visited nodes to detect loops.

Here’s an example:

def has_cycle_recursive(node, visited=set()):
    if node is None:
        return False
    if node in visited:
        return True
    visited.add(node)
    return has_cycle_recursive(node.next, visited)

# Same example usage as Method 1
print(has_cycle_recursive(node1))

Output: True

This one-liner recursive function, has_cycle_recursive, elegantly checks for a cycle by maintaining a set of visited nodes which is passed along with each recursive call. When a node is encountered that’s already in the set, it identifies a loop and returns True.

Summary/Discussion

  • Method 1: Floyd’s Cycle-Finding Algorithm. Strengths: No additional storage required. Fast. Weaknesses: Can be slightly complex to understand at first.
  • Method 2: Hash Table Approach. Strengths: Simple and straightforward to implement. Weaknesses: Requires O(n) additional memory for the hash table.
  • Method 3: Brent’s Algorithm. Strengths: More efficient than Floyd’s in practice, fewer iterations on average. Weaknesses: More complex to implement and understand.
  • Method 4: Reverse List Approach. Strengths: Thinking outside of box; simple idea. Weaknesses: Doesn’t actually work for detecting cycles, invalid method.
  • Bonus Method 5: Set Membership in Recursion. Strengths: Compact and elegant. Weaknesses: Could cause a stack overflow for very long lists due to recursive calls.