Skip to content

Commit 0aa3f8d

Browse files
Zzockervil02
andauthored
add algorithms to detect cycle in graph (#721)
* add algorithms to detect cycle in graph Signed-off-by: Pritam Singh <[email protected]> * parametrized test Signed-off-by: Pritam Singh <[email protected]> --------- Signed-off-by: Pritam Singh <[email protected]> Co-authored-by: Piotr Idzik <[email protected]>
1 parent d920c9b commit 0aa3f8d

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed

DIRECTORY.md

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

src/graph/detect_cycle.rs

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
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: Option<&'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 matches!(parent, Some(parent) if v == parent) {
20+
continue;
21+
}
22+
if visited_node.contains(v)
23+
|| undirected_graph_detect_cycle_dfs(graph, visited_node, Some(u), v)
24+
{
25+
return true;
26+
}
27+
}
28+
false
29+
}
30+
31+
// Helper function to detect cycle in an undirected graph using BFS graph traversal
32+
fn undirected_graph_detect_cycle_bfs<'a>(
33+
graph: &'a UndirectedGraph,
34+
visited_node: &mut HashSet<&'a String>,
35+
u: &'a String,
36+
) -> bool {
37+
visited_node.insert(u);
38+
39+
// Initialize the queue for BFS, storing (current node, parent node) tuples
40+
let mut queue = VecDeque::<(&String, Option<&String>)>::new();
41+
queue.push_back((u, None));
42+
43+
while let Some((u, parent)) = queue.pop_front() {
44+
for (v, _) in graph.adjacency_table().get(u).unwrap() {
45+
if matches!(parent, Some(parent) if v == parent) {
46+
continue;
47+
}
48+
if visited_node.contains(v) {
49+
return true;
50+
}
51+
visited_node.insert(v);
52+
queue.push_back((v, Some(u)));
53+
}
54+
}
55+
false
56+
}
57+
58+
impl DetectCycle for UndirectedGraph {
59+
fn detect_cycle_dfs(&self) -> bool {
60+
let mut visited_node = HashSet::<&String>::new();
61+
let adj = self.adjacency_table();
62+
for u in adj.keys() {
63+
if !visited_node.contains(u)
64+
&& undirected_graph_detect_cycle_dfs(self, &mut visited_node, None, u)
65+
{
66+
return true;
67+
}
68+
}
69+
false
70+
}
71+
72+
fn detect_cycle_bfs(&self) -> bool {
73+
let mut visited_node = HashSet::<&String>::new();
74+
let adj = self.adjacency_table();
75+
for u in adj.keys() {
76+
if !visited_node.contains(u)
77+
&& undirected_graph_detect_cycle_bfs(self, &mut visited_node, u)
78+
{
79+
return true;
80+
}
81+
}
82+
false
83+
}
84+
}
85+
86+
// Helper function to detect cycle in a directed graph using DFS graph traversal
87+
fn directed_graph_detect_cycle_dfs<'a>(
88+
graph: &'a DirectedGraph,
89+
visited_node: &mut HashSet<&'a String>,
90+
in_stack_visited_node: &mut HashSet<&'a String>,
91+
u: &'a String,
92+
) -> bool {
93+
visited_node.insert(u);
94+
in_stack_visited_node.insert(u);
95+
for (v, _) in graph.adjacency_table().get(u).unwrap() {
96+
if visited_node.contains(v) && in_stack_visited_node.contains(v) {
97+
return true;
98+
}
99+
if !visited_node.contains(v)
100+
&& directed_graph_detect_cycle_dfs(graph, visited_node, in_stack_visited_node, v)
101+
{
102+
return true;
103+
}
104+
}
105+
in_stack_visited_node.remove(u);
106+
false
107+
}
108+
109+
impl DetectCycle for DirectedGraph {
110+
fn detect_cycle_dfs(&self) -> bool {
111+
let mut visited_node = HashSet::<&String>::new();
112+
let mut in_stack_visited_node = HashSet::<&String>::new();
113+
let adj = self.adjacency_table();
114+
for u in adj.keys() {
115+
if !visited_node.contains(u)
116+
&& directed_graph_detect_cycle_dfs(
117+
self,
118+
&mut visited_node,
119+
&mut in_stack_visited_node,
120+
u,
121+
)
122+
{
123+
return true;
124+
}
125+
}
126+
false
127+
}
128+
129+
// detect cycle in a the graph using Kahn's algorithm
130+
// https://www.geeksforgeeks.org/detect-cycle-in-a-directed-graph-using-bfs/
131+
fn detect_cycle_bfs(&self) -> bool {
132+
// Set 0 in-degree for each vertex
133+
let mut in_degree: HashMap<&String, usize> =
134+
self.adjacency_table().keys().map(|k| (k, 0)).collect();
135+
136+
// Calculate in-degree for each vertex
137+
for u in self.adjacency_table().keys() {
138+
for (v, _) in self.adjacency_table().get(u).unwrap() {
139+
*in_degree.get_mut(v).unwrap() += 1;
140+
}
141+
}
142+
// Initialize queue with vertex having 0 in-degree
143+
let mut queue: VecDeque<&String> = in_degree
144+
.iter()
145+
.filter(|(_, &degree)| degree == 0)
146+
.map(|(&k, _)| k)
147+
.collect();
148+
149+
let mut count = 0;
150+
while let Some(u) = queue.pop_front() {
151+
count += 1;
152+
for (v, _) in self.adjacency_table().get(u).unwrap() {
153+
in_degree.entry(v).and_modify(|d| {
154+
*d -= 1;
155+
if *d == 0 {
156+
queue.push_back(v);
157+
}
158+
});
159+
}
160+
}
161+
162+
// If count of processed vertices is not equal to the number of vertices,
163+
// the graph has a cycle
164+
count != self.adjacency_table().len()
165+
}
166+
}
167+
168+
#[cfg(test)]
169+
mod test {
170+
use super::DetectCycle;
171+
use crate::data_structures::{graph::Graph, DirectedGraph, UndirectedGraph};
172+
fn get_undirected_single_node_with_loop() -> UndirectedGraph {
173+
let mut res = UndirectedGraph::new();
174+
res.add_edge(("a", "a", 1));
175+
res
176+
}
177+
fn get_directed_single_node_with_loop() -> DirectedGraph {
178+
let mut res = DirectedGraph::new();
179+
res.add_edge(("a", "a", 1));
180+
res
181+
}
182+
fn get_undirected_two_nodes_connected() -> UndirectedGraph {
183+
let mut res = UndirectedGraph::new();
184+
res.add_edge(("a", "b", 1));
185+
res
186+
}
187+
fn get_directed_two_nodes_connected() -> DirectedGraph {
188+
let mut res = DirectedGraph::new();
189+
res.add_edge(("a", "b", 1));
190+
res.add_edge(("b", "a", 1));
191+
res
192+
}
193+
fn get_directed_two_nodes() -> DirectedGraph {
194+
let mut res = DirectedGraph::new();
195+
res.add_edge(("a", "b", 1));
196+
res
197+
}
198+
fn get_undirected_triangle() -> UndirectedGraph {
199+
let mut res = UndirectedGraph::new();
200+
res.add_edge(("a", "b", 1));
201+
res.add_edge(("b", "c", 1));
202+
res.add_edge(("c", "a", 1));
203+
res
204+
}
205+
fn get_directed_triangle() -> DirectedGraph {
206+
let mut res = DirectedGraph::new();
207+
res.add_edge(("a", "b", 1));
208+
res.add_edge(("b", "c", 1));
209+
res.add_edge(("c", "a", 1));
210+
res
211+
}
212+
fn get_undirected_triangle_with_tail() -> UndirectedGraph {
213+
let mut res = get_undirected_triangle();
214+
res.add_edge(("c", "d", 1));
215+
res.add_edge(("d", "e", 1));
216+
res.add_edge(("e", "f", 1));
217+
res.add_edge(("g", "h", 1));
218+
res
219+
}
220+
fn get_directed_triangle_with_tail() -> DirectedGraph {
221+
let mut res = get_directed_triangle();
222+
res.add_edge(("c", "d", 1));
223+
res.add_edge(("d", "e", 1));
224+
res.add_edge(("e", "f", 1));
225+
res.add_edge(("g", "h", 1));
226+
res
227+
}
228+
fn get_undirected_graph_with_cycle() -> UndirectedGraph {
229+
let mut res = UndirectedGraph::new();
230+
res.add_edge(("a", "b", 1));
231+
res.add_edge(("a", "c", 1));
232+
res.add_edge(("b", "c", 1));
233+
res.add_edge(("b", "d", 1));
234+
res.add_edge(("c", "d", 1));
235+
res
236+
}
237+
fn get_undirected_graph_without_cycle() -> UndirectedGraph {
238+
let mut res = UndirectedGraph::new();
239+
res.add_edge(("a", "b", 1));
240+
res.add_edge(("a", "c", 1));
241+
res.add_edge(("b", "d", 1));
242+
res.add_edge(("c", "e", 1));
243+
res
244+
}
245+
fn get_directed_graph_with_cycle() -> DirectedGraph {
246+
let mut res = DirectedGraph::new();
247+
res.add_edge(("b", "a", 1));
248+
res.add_edge(("c", "a", 1));
249+
res.add_edge(("b", "c", 1));
250+
res.add_edge(("c", "d", 1));
251+
res.add_edge(("d", "b", 1));
252+
res
253+
}
254+
fn get_directed_graph_without_cycle() -> DirectedGraph {
255+
let mut res = DirectedGraph::new();
256+
res.add_edge(("b", "a", 1));
257+
res.add_edge(("c", "a", 1));
258+
res.add_edge(("b", "c", 1));
259+
res.add_edge(("c", "d", 1));
260+
res.add_edge(("b", "d", 1));
261+
res
262+
}
263+
macro_rules! test_detect_cycle {
264+
($($name:ident: $test_case:expr,)*) => {
265+
$(
266+
#[test]
267+
fn $name() {
268+
let (graph, has_cycle) = $test_case;
269+
println!("detect_cycle_dfs: {}", graph.detect_cycle_dfs());
270+
println!("detect_cycle_bfs: {}", graph.detect_cycle_bfs());
271+
assert_eq!(graph.detect_cycle_dfs(), has_cycle);
272+
assert_eq!(graph.detect_cycle_bfs(), has_cycle);
273+
}
274+
)*
275+
};
276+
}
277+
test_detect_cycle! {
278+
undirected_empty: (UndirectedGraph::new(), false),
279+
directed_empty: (DirectedGraph::new(), false),
280+
undirected_single_node_with_loop: (get_undirected_single_node_with_loop(), true),
281+
directed_single_node_with_loop: (get_directed_single_node_with_loop(), true),
282+
undirected_two_nodes_connected: (get_undirected_two_nodes_connected(), false),
283+
directed_two_nodes_connected: (get_directed_two_nodes_connected(), true),
284+
directed_two_nodes: (get_directed_two_nodes(), false),
285+
undirected_triangle: (get_undirected_triangle(), true),
286+
undirected_triangle_with_tail: (get_undirected_triangle_with_tail(), true),
287+
directed_triangle: (get_directed_triangle(), true),
288+
directed_triangle_with_tail: (get_directed_triangle_with_tail(), true),
289+
undirected_graph_with_cycle: (get_undirected_graph_with_cycle(), true),
290+
undirected_graph_without_cycle: (get_undirected_graph_without_cycle(), false),
291+
directed_graph_with_cycle: (get_directed_graph_with_cycle(), true),
292+
directed_graph_without_cycle: (get_directed_graph_without_cycle(), false),
293+
}
294+
}

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)