Skip to content

Commit 3012206

Browse files
Add Floyd's Cycle Detection Algorithm (#10833)
* Add Floyd's Cycle Detection Algorithm * Add tests for add_node function * Apply suggestions from code review * Update floyds_cycle_detection.py --------- Co-authored-by: Tianyi Zheng <[email protected]>
1 parent 54e2aa6 commit 3012206

File tree

1 file changed

+150
-0
lines changed

1 file changed

+150
-0
lines changed
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""
2+
Floyd's cycle detection algorithm is a popular algorithm used to detect cycles
3+
in a linked list. It uses two pointers, a slow pointer and a fast pointer,
4+
to traverse the linked list. The slow pointer moves one node at a time while the fast
5+
pointer moves two nodes at a time. If there is a cycle in the linked list,
6+
the fast pointer will eventually catch up to the slow pointer and they will
7+
meet at the same node. If there is no cycle, the fast pointer will reach the end of
8+
the linked list and the algorithm will terminate.
9+
10+
For more information: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
11+
"""
12+
13+
from collections.abc import Iterator
14+
from dataclasses import dataclass
15+
from typing import Any, Self
16+
17+
18+
@dataclass
19+
class Node:
20+
"""
21+
A class representing a node in a singly linked list.
22+
"""
23+
24+
data: Any
25+
next_node: Self | None = None
26+
27+
28+
@dataclass
29+
class LinkedList:
30+
"""
31+
A class representing a singly linked list.
32+
"""
33+
34+
head: Node | None = None
35+
36+
def __iter__(self) -> Iterator:
37+
"""
38+
Iterates through the linked list.
39+
40+
Returns:
41+
Iterator: An iterator over the linked list.
42+
43+
Examples:
44+
>>> linked_list = LinkedList()
45+
>>> list(linked_list)
46+
[]
47+
>>> linked_list.add_node(1)
48+
>>> tuple(linked_list)
49+
(1,)
50+
"""
51+
visited = []
52+
node = self.head
53+
while node:
54+
# Avoid infinite loop in there's a cycle
55+
if node in visited:
56+
return
57+
visited.append(node)
58+
yield node.data
59+
node = node.next_node
60+
61+
def add_node(self, data: Any) -> None:
62+
"""
63+
Adds a new node to the end of the linked list.
64+
65+
Args:
66+
data (Any): The data to be stored in the new node.
67+
68+
Examples:
69+
>>> linked_list = LinkedList()
70+
>>> linked_list.add_node(1)
71+
>>> linked_list.add_node(2)
72+
>>> linked_list.add_node(3)
73+
>>> linked_list.add_node(4)
74+
>>> tuple(linked_list)
75+
(1, 2, 3, 4)
76+
"""
77+
new_node = Node(data)
78+
79+
if self.head is None:
80+
self.head = new_node
81+
return
82+
83+
current_node = self.head
84+
while current_node.next_node is not None:
85+
current_node = current_node.next_node
86+
87+
current_node.next_node = new_node
88+
89+
def detect_cycle(self) -> bool:
90+
"""
91+
Detects if there is a cycle in the linked list using
92+
Floyd's cycle detection algorithm.
93+
94+
Returns:
95+
bool: True if there is a cycle, False otherwise.
96+
97+
Examples:
98+
>>> linked_list = LinkedList()
99+
>>> linked_list.add_node(1)
100+
>>> linked_list.add_node(2)
101+
>>> linked_list.add_node(3)
102+
>>> linked_list.add_node(4)
103+
104+
>>> linked_list.detect_cycle()
105+
False
106+
107+
# Create a cycle in the linked list
108+
>>> linked_list.head.next_node.next_node.next_node = linked_list.head.next_node
109+
110+
>>> linked_list.detect_cycle()
111+
True
112+
"""
113+
if self.head is None:
114+
return False
115+
116+
slow_pointer: Node | None = self.head
117+
fast_pointer: Node | None = self.head
118+
119+
while fast_pointer is not None and fast_pointer.next_node is not None:
120+
slow_pointer = slow_pointer.next_node if slow_pointer else None
121+
fast_pointer = fast_pointer.next_node.next_node
122+
if slow_pointer == fast_pointer:
123+
return True
124+
125+
return False
126+
127+
128+
if __name__ == "__main__":
129+
import doctest
130+
131+
doctest.testmod()
132+
133+
linked_list = LinkedList()
134+
linked_list.add_node(1)
135+
linked_list.add_node(2)
136+
linked_list.add_node(3)
137+
linked_list.add_node(4)
138+
139+
# Create a cycle in the linked list
140+
# It first checks if the head, next_node, and next_node.next_node attributes of the
141+
# linked list are not None to avoid any potential type errors.
142+
if (
143+
linked_list.head
144+
and linked_list.head.next_node
145+
and linked_list.head.next_node.next_node
146+
):
147+
linked_list.head.next_node.next_node.next_node = linked_list.head.next_node
148+
149+
has_cycle = linked_list.detect_cycle()
150+
print(has_cycle) # Output: True

0 commit comments

Comments
 (0)