Skip to content

Commit c70e8ec

Browse files
committed
add algorithms to detect cycle in graph
Signed-off-by: Pritam Singh <[email protected]>
1 parent b17c532 commit c70e8ec

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
* [Tarjans Ssc](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/tarjans_ssc.rs)
144144
* [Topological Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/topological_sort.rs)
145145
* [Two Satisfiability](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/two_satisfiability.rs)
146+
* [Cycle Detection](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/detect_cycle.rs)
146147
* [Lib](https://github.com/TheAlgorithms/Rust/blob/master/src/lib.rs)
147148
* Machine Learning
148149
* [Cholesky](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/cholesky.rs)

src/graph/detect_cycle.rs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use std::collections::{HashMap, HashSet, VecDeque};
2+
3+
use crate::data_structures::{graph::Graph, DirectedGraph, UndirectedGraph};
4+
5+
pub trait DetectCycle {
6+
fn detect_cycle_dfs(&self) -> bool;
7+
fn detect_cycle_bfs(&self) -> bool;
8+
}
9+
10+
// Helper function to detect cycle in an undirected graph using DFS graph traversal
11+
fn undirected_graph_detect_cycle_dfs<'a>(
12+
graph: &'a UndirectedGraph,
13+
visited_node: &mut HashSet<&'a String>,
14+
parent: &'a String,
15+
u: &'a String,
16+
) -> bool {
17+
visited_node.insert(u);
18+
for (v, _) in graph.adjacency_table().get(u).unwrap() {
19+
if v == parent {
20+
continue;
21+
}
22+
if visited_node.contains(v) || undirected_graph_detect_cycle_dfs(graph, visited_node, u, v)
23+
{
24+
return true;
25+
}
26+
}
27+
false
28+
}
29+
30+
// Helper function to detect cycle in an undirected graph using BFS graph traversal
31+
fn undirected_graph_detect_cycle_bfs<'a>(
32+
graph: &'a UndirectedGraph,
33+
visited_node: &mut HashSet<&'a String>,
34+
u: &'a String,
35+
) -> bool {
36+
visited_node.insert(u);
37+
38+
// Initialize the queue for BFS, storing (current node, parent node) tuples
39+
let mut queue = VecDeque::<(&String, &String)>::new();
40+
queue.push_back((u, u));
41+
42+
while let Some((u, parent)) = queue.pop_front() {
43+
for (v, _) in graph.adjacency_table().get(u).unwrap() {
44+
if v == parent {
45+
continue;
46+
}
47+
if visited_node.contains(v) {
48+
return true;
49+
}
50+
visited_node.insert(v);
51+
queue.push_back((v, u));
52+
}
53+
}
54+
false
55+
}
56+
57+
impl DetectCycle for UndirectedGraph {
58+
fn detect_cycle_dfs(&self) -> bool {
59+
let mut visited_node = HashSet::<&String>::new();
60+
let adj = self.adjacency_table();
61+
for u in adj.keys() {
62+
if !visited_node.contains(u)
63+
&& undirected_graph_detect_cycle_dfs(self, &mut visited_node, u, u)
64+
{
65+
return true;
66+
}
67+
}
68+
false
69+
}
70+
71+
fn detect_cycle_bfs(&self) -> bool {
72+
let mut visited_node = HashSet::<&String>::new();
73+
let adj = self.adjacency_table();
74+
for u in adj.keys() {
75+
if !visited_node.contains(u)
76+
&& undirected_graph_detect_cycle_bfs(self, &mut visited_node, u)
77+
{
78+
return true;
79+
}
80+
}
81+
false
82+
}
83+
}
84+
85+
// Helper function to detect cycle in a directed graph using DFS graph traversal
86+
fn directed_graph_detect_cycle_dfs<'a>(
87+
graph: &'a DirectedGraph,
88+
visited_node: &mut HashSet<&'a String>,
89+
in_stack_visited_node: &mut HashSet<&'a String>,
90+
u: &'a String,
91+
) -> bool {
92+
visited_node.insert(u);
93+
in_stack_visited_node.insert(u);
94+
for (v, _) in graph.adjacency_table().get(u).unwrap() {
95+
if visited_node.contains(v) && in_stack_visited_node.contains(v) {
96+
return true;
97+
}
98+
if !visited_node.contains(v)
99+
&& directed_graph_detect_cycle_dfs(graph, visited_node, in_stack_visited_node, v)
100+
{
101+
return true;
102+
}
103+
}
104+
in_stack_visited_node.remove(u);
105+
false
106+
}
107+
108+
impl DetectCycle for DirectedGraph {
109+
fn detect_cycle_dfs(&self) -> bool {
110+
let mut visited_node = HashSet::<&String>::new();
111+
let mut in_stack_visited_node = HashSet::<&String>::new();
112+
let adj = self.adjacency_table();
113+
for u in adj.keys() {
114+
if !visited_node.contains(u)
115+
&& directed_graph_detect_cycle_dfs(
116+
self,
117+
&mut visited_node,
118+
&mut in_stack_visited_node,
119+
u,
120+
)
121+
{
122+
return true;
123+
}
124+
}
125+
false
126+
}
127+
128+
// detect cycle in a the graph using Kahn's algorithm
129+
// https://www.geeksforgeeks.org/detect-cycle-in-a-directed-graph-using-bfs/
130+
fn detect_cycle_bfs(&self) -> bool {
131+
// Set 0 in-degree for each vertex
132+
let mut in_degree: HashMap<&String, usize> =
133+
self.adjacency_table().keys().map(|k| (k, 0)).collect();
134+
135+
// Calculate in-degree for each vertex
136+
for u in self.adjacency_table().keys() {
137+
for (v, _) in self.adjacency_table().get(u).unwrap() {
138+
*in_degree.get_mut(v).unwrap() += 1;
139+
}
140+
}
141+
// Initialize queue with vertex having 0 in-degree
142+
let mut queue: VecDeque<&String> = in_degree
143+
.iter()
144+
.filter(|(_, &degree)| degree == 0)
145+
.map(|(&k, _)| k)
146+
.collect();
147+
148+
let mut count = 0;
149+
while let Some(u) = queue.pop_front() {
150+
count += 1;
151+
for (v, _) in self.adjacency_table().get(u).unwrap() {
152+
in_degree.entry(v).and_modify(|d| {
153+
*d -= 1;
154+
if *d == 0 {
155+
queue.push_back(v);
156+
}
157+
});
158+
}
159+
}
160+
161+
// If count of processed vertices is not equal to the number of vertices,
162+
// the graph has a cycle
163+
count != self.adjacency_table().len()
164+
}
165+
}
166+
167+
#[cfg(test)]
168+
mod test {
169+
use crate::data_structures::{graph::Graph, DirectedGraph, UndirectedGraph};
170+
171+
use super::DetectCycle;
172+
173+
#[test]
174+
fn test_detect_cycle_in_undirected_graph() {
175+
let mut graph_with_cycle = UndirectedGraph::new();
176+
177+
graph_with_cycle.add_edge(("a", "b", 1));
178+
graph_with_cycle.add_edge(("a", "c", 1));
179+
graph_with_cycle.add_edge(("b", "c", 1));
180+
graph_with_cycle.add_edge(("b", "d", 1));
181+
graph_with_cycle.add_edge(("c", "d", 1));
182+
183+
assert!(graph_with_cycle.detect_cycle_dfs());
184+
assert!(graph_with_cycle.detect_cycle_bfs());
185+
186+
let mut graph_without_cycle = UndirectedGraph::new();
187+
188+
graph_without_cycle.add_edge(("a", "b", 1));
189+
graph_without_cycle.add_edge(("a", "c", 1));
190+
graph_without_cycle.add_edge(("b", "d", 1));
191+
graph_without_cycle.add_edge(("c", "e", 1));
192+
193+
assert!(!graph_without_cycle.detect_cycle_dfs());
194+
assert!(!graph_without_cycle.detect_cycle_bfs());
195+
}
196+
197+
#[test]
198+
fn test_detect_cycle_in_directed_graph() {
199+
let mut graph_with_cycle = DirectedGraph::new();
200+
201+
graph_with_cycle.add_edge(("b", "a", 1));
202+
graph_with_cycle.add_edge(("c", "a", 1));
203+
graph_with_cycle.add_edge(("b", "c", 1));
204+
graph_with_cycle.add_edge(("c", "d", 1));
205+
graph_with_cycle.add_edge(("d", "b", 1));
206+
207+
assert!(graph_with_cycle.detect_cycle_dfs());
208+
assert!(graph_with_cycle.detect_cycle_bfs());
209+
210+
let mut graph_without_cycle = DirectedGraph::new();
211+
212+
graph_without_cycle.add_edge(("b", "a", 1));
213+
graph_without_cycle.add_edge(("c", "a", 1));
214+
graph_without_cycle.add_edge(("b", "c", 1));
215+
graph_without_cycle.add_edge(("c", "d", 1));
216+
graph_without_cycle.add_edge(("b", "d", 1));
217+
218+
assert!(!graph_without_cycle.detect_cycle_dfs());
219+
assert!(!graph_without_cycle.detect_cycle_bfs());
220+
}
221+
}

src/graph/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod centroid_decomposition;
66
mod decremental_connectivity;
77
mod depth_first_search;
88
mod depth_first_search_tic_tac_toe;
9+
mod detect_cycle;
910
mod dijkstra;
1011
mod dinic_maxflow;
1112
mod disjoint_set_union;
@@ -33,6 +34,7 @@ pub use self::centroid_decomposition::CentroidDecomposition;
3334
pub use self::decremental_connectivity::DecrementalConnectivity;
3435
pub use self::depth_first_search::depth_first_search;
3536
pub use self::depth_first_search_tic_tac_toe::minimax;
37+
pub use self::detect_cycle::DetectCycle;
3638
pub use self::dijkstra::dijkstra;
3739
pub use self::dinic_maxflow::DinicMaxFlow;
3840
pub use self::disjoint_set_union::DisjointSetUnion;

0 commit comments

Comments
 (0)