use crate::graph::{NodeId, Successors};
pub fn has_cycle<G: Successors>(graph: &G, start: NodeId) -> bool {
let node_count = graph.node_count();
if start.index() >= node_count {
return false;
}
let mut visited = vec![false; node_count];
let mut in_stack = vec![false; node_count];
has_cycle_dfs(graph, start, &mut visited, &mut in_stack)
}
fn has_cycle_dfs<G: Successors>(
graph: &G,
start: NodeId,
visited: &mut [bool],
in_stack: &mut [bool],
) -> bool {
let start_idx = start.index();
if visited.get(start_idx).copied().unwrap_or(false) {
return false;
}
if let Some(slot) = visited.get_mut(start_idx) {
*slot = true;
}
if let Some(slot) = in_stack.get_mut(start_idx) {
*slot = true;
}
let mut stack: Vec<(NodeId, std::vec::IntoIter<NodeId>)> = vec![(
start,
graph.successors(start).collect::<Vec<_>>().into_iter(),
)];
while !stack.is_empty() {
let next = match stack.last_mut() {
Some(frame) => frame.1.next(),
None => break,
};
match next {
Some(successor) => {
let s_idx = successor.index();
if in_stack.get(s_idx).copied().unwrap_or(false) {
return true;
}
if visited.get(s_idx).copied().unwrap_or(false) {
continue;
}
if let Some(slot) = visited.get_mut(s_idx) {
*slot = true;
}
if let Some(slot) = in_stack.get_mut(s_idx) {
*slot = true;
}
stack.push((
successor,
graph.successors(successor).collect::<Vec<_>>().into_iter(),
));
}
None => {
if let Some((node, _)) = stack.pop() {
if let Some(slot) = in_stack.get_mut(node.index()) {
*slot = false;
}
}
}
}
}
false
}
pub fn find_cycle<G: Successors>(graph: &G, start: NodeId) -> Option<Vec<NodeId>> {
let node_count = graph.node_count();
if start.index() >= node_count {
return None;
}
let mut visited = vec![false; node_count];
let mut in_stack = vec![false; node_count];
let mut path = Vec::new();
find_cycle_dfs(graph, start, &mut visited, &mut in_stack, &mut path)
}
fn find_cycle_dfs<G: Successors>(
graph: &G,
start: NodeId,
visited: &mut [bool],
in_stack: &mut [bool],
path: &mut Vec<NodeId>,
) -> Option<Vec<NodeId>> {
let start_idx = start.index();
if visited.get(start_idx).copied().unwrap_or(false) {
return None;
}
if let Some(slot) = visited.get_mut(start_idx) {
*slot = true;
}
if let Some(slot) = in_stack.get_mut(start_idx) {
*slot = true;
}
path.push(start);
let mut stack: Vec<(NodeId, std::vec::IntoIter<NodeId>)> = vec![(
start,
graph.successors(start).collect::<Vec<_>>().into_iter(),
)];
while !stack.is_empty() {
let next = match stack.last_mut() {
Some(frame) => frame.1.next(),
None => break,
};
match next {
Some(successor) => {
let s_idx = successor.index();
if in_stack.get(s_idx).copied().unwrap_or(false) {
let cycle_start_pos = path.iter().position(|&n| n == successor)?;
let mut cycle: Vec<NodeId> = path.get(cycle_start_pos..)?.to_vec();
cycle.push(successor); return Some(cycle);
}
if visited.get(s_idx).copied().unwrap_or(false) {
continue;
}
if let Some(slot) = visited.get_mut(s_idx) {
*slot = true;
}
if let Some(slot) = in_stack.get_mut(s_idx) {
*slot = true;
}
path.push(successor);
stack.push((
successor,
graph.successors(successor).collect::<Vec<_>>().into_iter(),
));
}
None => {
if let Some((node, _)) = stack.pop() {
if let Some(slot) = in_stack.get_mut(node.index()) {
*slot = false;
}
path.pop();
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use crate::graph::{
algorithms::cycles::{find_cycle, has_cycle},
DirectedGraph, NodeId,
};
fn create_linear_graph() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph
}
fn create_diamond_graph() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(a, c, ()).unwrap();
graph.add_edge(b, d, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph
}
fn create_simple_cycle() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, a, ()).unwrap();
graph
}
fn create_self_loop() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
graph.add_edge(a, a, ()).unwrap();
graph
}
fn create_complex_with_cycle() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, b, ()).unwrap();
graph
}
#[test]
fn test_has_cycle_linear() {
let graph = create_linear_graph();
assert!(!has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_diamond() {
let graph = create_diamond_graph();
assert!(!has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_simple_cycle() {
let graph = create_simple_cycle();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_self_loop() {
let graph = create_self_loop();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_complex() {
let graph = create_complex_with_cycle();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_single_node_no_loop() {
let mut graph: DirectedGraph<(), ()> = DirectedGraph::new();
let a = graph.add_node(());
assert!(!has_cycle(&graph, a));
}
#[test]
fn test_has_cycle_two_separate_cycles() {
let mut graph: DirectedGraph<&str, ()> = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
assert!(has_cycle(&graph, a));
assert!(has_cycle(&graph, c));
}
#[test]
fn test_find_cycle_linear() {
let graph = create_linear_graph();
assert!(find_cycle(&graph, NodeId::new(0)).is_none());
}
#[test]
fn test_find_cycle_diamond() {
let graph = create_diamond_graph();
assert!(find_cycle(&graph, NodeId::new(0)).is_none());
}
#[test]
fn test_find_cycle_simple_cycle() {
let graph = create_simple_cycle();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.first(), cycle.last());
assert!(cycle.len() >= 3);
}
#[test]
fn test_find_cycle_self_loop() {
let graph = create_self_loop();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.len(), 2);
assert_eq!(cycle[0], cycle[1]);
}
#[test]
fn test_find_cycle_complex() {
let graph = create_complex_with_cycle();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.first(), cycle.last());
}
#[test]
fn test_find_cycle_returns_valid_path() {
let graph = create_simple_cycle();
let cycle = find_cycle(&graph, NodeId::new(0)).unwrap();
for i in 0..cycle.len() - 1 {
let current = cycle[i];
let next = cycle[i + 1];
let successors: Vec<NodeId> = graph.successors(current).collect();
assert!(
successors.contains(&next),
"Invalid cycle path: no edge from {:?} to {:?}",
current,
next
);
}
}
#[test]
fn test_has_cycle_deep_linear_chain_is_iterative() {
let mut graph: DirectedGraph<(), ()> = DirectedGraph::new();
let mut nodes = Vec::new();
for _ in 0..10_000 {
nodes.push(graph.add_node(()));
}
for window in nodes.windows(2) {
graph.add_edge(window[0], window[1], ()).unwrap();
}
assert!(!has_cycle(&graph, nodes[0]));
assert!(find_cycle(&graph, nodes[0]).is_none());
graph
.add_edge(nodes[nodes.len() - 1], nodes[0], ())
.unwrap();
assert!(has_cycle(&graph, nodes[0]));
let cycle = find_cycle(&graph, nodes[0]).unwrap();
assert_eq!(cycle.first(), cycle.last());
}
#[test]
fn test_find_cycle_disconnected_cycle() {
let mut graph: DirectedGraph<&str, ()> = DirectedGraph::new();
let entry = graph.add_node("Entry");
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(entry, a, ()).unwrap();
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, a, ()).unwrap();
let cycle = find_cycle(&graph, entry);
assert!(cycle.is_some());
}
}