Skip to content

Commit 1362587

Browse files
committed
[Feature] Added cycle detection to graphs.
1 parent 86034da commit 1362587

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ $ coverage report -m
5353
* [Linked List](structures/linked_list.py)
5454
* [Contains Cycles](structures/linked_list.py)
5555
* [Find Start of Cycle](structures/linked_list.py)
56+
* [Graph](structures/graph.py)
57+
* [Detect Cycle](structures/graph.py)
5658

5759
### Algorithms
5860

structures/graph.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,45 @@ def add_edge(self, source, destination):
3535
"""
3636
self.graph[source].append(destination)
3737

38+
def has_cycle(self):
39+
"""
40+
Detect if a graph has a cycle.
41+
42+
Returns:
43+
True if the graph has a cycle and
44+
False if the graph is acyclic.
45+
46+
"""
47+
48+
visited = [0] * self.verticies
49+
50+
def valid(node):
51+
# print(visited, node)
52+
if visited[node] == -1:
53+
return False
54+
elif visited[node] == 1:
55+
return True
56+
visited[node] = -1
57+
for neighbor in self.graph[node]:
58+
if not valid(neighbor):
59+
return False
60+
return True
61+
62+
for node in range(self.verticies):
63+
# dfs from each, mark as -1 when visited
64+
# after dfs set all -1 -> 1
65+
# cancel if find a 1, as its already been explored
66+
if not visited[node]:
67+
# print(visited)
68+
if valid(node):
69+
# set all -1 to 1
70+
visited = list(map(lambda x: abs(x),visited))
71+
else:
72+
# cycle!
73+
return True
74+
# no cycle available
75+
return False
76+
3877
def topological_sort(self):
3978
"""
4079
Sort the graph topologically.

tests/test_graph_cycles.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import unittest
2+
from structures.graph import Graph
3+
4+
class TestCycle(unittest.TestCase):
5+
6+
def test_cycles_true(self):
7+
8+
graph = Graph(4)
9+
10+
graph.add_edge(0,1)
11+
graph.add_edge(1,2)
12+
graph.add_edge(2,3)
13+
graph.add_edge(3,0)
14+
15+
self.assertEqual(True, graph.has_cycle())
16+
17+
def test_cycles_true_complex_graph(self):
18+
19+
graph = Graph(7)
20+
21+
graph.add_edge(0,1)
22+
graph.add_edge(1,2)
23+
graph.add_edge(3,2)
24+
graph.add_edge(4,3)
25+
graph.add_edge(4,6)
26+
graph.add_edge(5,3)
27+
graph.add_edge(5,4)
28+
graph.add_edge(6,5)
29+
30+
self.assertEqual(True, graph.has_cycle())
31+
32+
def test_cycles_true_2_node_graph(self):
33+
34+
graph = Graph(2)
35+
36+
graph.add_edge(0,1)
37+
graph.add_edge(1,0)
38+
39+
self.assertEqual(True, graph.has_cycle())
40+
41+
def test_cycle_with_discrete_forest(self):
42+
43+
graph = Graph(6)
44+
45+
graph.add_edge(0,1)
46+
graph.add_edge(1,2)
47+
graph.add_edge(3,4)
48+
graph.add_edge(4,5)
49+
graph.add_edge(5,3)
50+
51+
self.assertEqual(True, graph.has_cycle())
52+
53+
54+
def test_cycles_false(self):
55+
56+
graph = Graph(4)
57+
58+
graph.add_edge(0,1)
59+
graph.add_edge(1,2)
60+
graph.add_edge(2,3)
61+
62+
self.assertEqual(False, graph.has_cycle())

0 commit comments

Comments
 (0)