use crate::algorithms::properties::is_dag::is_dag;
use crate::core::Graph;
#[must_use]
pub fn is_acyclic(graph: &Graph) -> bool {
if graph.is_directed() {
return is_dag(graph);
}
let n = graph.vcount();
let m = graph.ecount();
let n_us = n as usize;
if n == 0 || m == 0 {
return true;
}
let mut parent: Vec<u32> = (0..n).collect();
let mut size: Vec<u32> = vec![1; n_us];
let Ok(m_u32) = u32::try_from(m) else {
return false;
};
for eid in 0..m_u32 {
let Ok((u, v)) = graph.edge(eid) else {
return false;
};
if u == v {
return false;
}
let mut ru = u as usize;
while parent[ru] as usize != ru {
parent[ru] = parent[parent[ru] as usize];
ru = parent[ru] as usize;
}
let mut rv = v as usize;
while parent[rv] as usize != rv {
parent[rv] = parent[parent[rv] as usize];
rv = parent[rv] as usize;
}
if ru == rv {
return false;
}
let (parent_idx, child_idx) = if size[ru] < size[rv] {
(rv, ru)
} else {
(ru, rv)
};
let Ok(parent_u32) = u32::try_from(parent_idx) else {
return false;
};
parent[child_idx] = parent_u32;
size[parent_idx] = size[parent_idx].saturating_add(size[child_idx]);
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Graph;
#[test]
fn empty_undirected_is_acyclic() {
let g = Graph::with_vertices(0);
assert!(is_acyclic(&g));
}
#[test]
fn isolated_vertices_only_is_acyclic() {
let g = Graph::with_vertices(5);
assert!(is_acyclic(&g));
}
#[test]
fn undirected_tree_is_acyclic() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
assert!(is_acyclic(&g));
}
#[test]
fn undirected_forest_is_acyclic() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0u32, 1u32), (1, 2), (3, 4)]).unwrap();
assert!(is_acyclic(&g));
}
#[test]
fn undirected_triangle_is_not_acyclic() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(!is_acyclic(&g));
}
#[test]
fn undirected_self_loop_is_not_acyclic() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert!(!is_acyclic(&g));
}
#[test]
fn undirected_parallel_edge_is_not_acyclic() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert!(!is_acyclic(&g));
}
#[test]
fn undirected_star_is_acyclic() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (0, 4)])
.unwrap();
assert!(is_acyclic(&g));
}
#[test]
fn directed_dag_chain_is_acyclic() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
assert!(is_acyclic(&g));
}
#[test]
fn directed_cycle_is_not_acyclic() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(!is_acyclic(&g));
}
#[test]
fn directed_self_loop_is_not_acyclic() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert!(!is_acyclic(&g));
}
#[test]
fn directed_diamond_dag_is_acyclic() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3), (2, 3)])
.unwrap();
assert!(is_acyclic(&g));
}
#[test]
fn undirected_no_edges_with_many_vertices_is_acyclic() {
let g = Graph::with_vertices(100);
assert!(is_acyclic(&g));
}
}