use crate::algorithms::paths::dijkstra::DijkstraMode;
use crate::core::Graph;
use crate::core::error::IgraphResult;
use crate::core::graph::{EdgeId, VertexId};
pub fn is_forest(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Option<Vec<VertexId>>> {
let n = graph.vcount();
let m = graph.ecount();
if n == 0 {
return Ok(Some(Vec::new()));
}
if m == 0 {
return Ok(Some((0..n).collect()));
}
if m as u64 > u64::from(n).saturating_sub(1) {
return Ok(None);
}
let directed = graph.is_directed();
let effective_mode = if directed { mode } else { DijkstraMode::All };
let n_us = n as usize;
let mut visited = vec![false; n_us];
let mut visited_count: u32 = 0;
let roots_opt = collect_roots_and_traverse(
graph,
n,
effective_mode,
directed,
&mut visited,
&mut visited_count,
)?;
let Some(roots) = roots_opt else {
return Ok(None);
};
if visited_count == n {
Ok(Some(roots))
} else {
Ok(None)
}
}
fn collect_roots_and_traverse(
graph: &Graph,
n: VertexId,
mode: DijkstraMode,
directed: bool,
visited: &mut [bool],
visited_count: &mut u32,
) -> IgraphResult<Option<Vec<VertexId>>> {
let mut roots: Vec<VertexId> = Vec::new();
match mode {
DijkstraMode::All => {
for v in 0..n {
if !visited[v as usize] {
roots.push(v);
if !is_forest_visitor(graph, v, mode, directed, visited, visited_count)? {
return Ok(None);
}
}
}
}
DijkstraMode::Out | DijkstraMode::In => {
let use_in_degree = matches!(mode, DijkstraMode::Out);
for v in 0..n {
let d = counter_degree(graph, v, use_in_degree)?;
if d > 1 {
return Ok(None);
}
if d == 0 {
roots.push(v);
if !is_forest_visitor(graph, v, mode, directed, visited, visited_count)? {
return Ok(None);
}
}
}
}
}
Ok(Some(roots))
}
fn counter_degree(graph: &Graph, v: VertexId, use_in_degree: bool) -> IgraphResult<usize> {
if use_in_degree {
Ok(graph.incident_in(v)?.len())
} else {
Ok(graph.incident(v)?.len())
}
}
fn is_forest_visitor(
graph: &Graph,
root: VertexId,
mode: DijkstraMode,
directed: bool,
visited: &mut [bool],
visited_count: &mut u32,
) -> IgraphResult<bool> {
let mut stack: Vec<VertexId> = Vec::new();
stack.push(root);
while let Some(u) = stack.pop() {
if visited[u as usize] {
return Ok(false);
}
visited[u as usize] = true;
*visited_count = visited_count.saturating_add(1);
for eid in incident_edges(graph, u, mode, directed)? {
let nei = graph.edge_other(eid, u)?;
match mode {
DijkstraMode::All => {
if !visited[nei as usize] {
stack.push(nei);
} else if nei == u {
return Ok(false);
}
}
DijkstraMode::Out | DijkstraMode::In => {
stack.push(nei);
}
}
}
}
Ok(true)
}
fn incident_edges(
graph: &Graph,
u: VertexId,
mode: DijkstraMode,
directed: bool,
) -> IgraphResult<Vec<EdgeId>> {
match mode {
DijkstraMode::Out => graph.incident(u),
DijkstraMode::In => graph.incident_in(u),
DijkstraMode::All => {
if directed {
let mut out = graph.incident(u)?;
out.extend(graph.incident_in(u)?);
Ok(out)
} else {
graph.incident(u)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_graph_is_forest_with_empty_roots() {
let g = Graph::with_vertices(0);
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert!(roots.is_empty());
}
#[test]
fn no_edges_undirected_every_vertex_is_a_root() {
let g = Graph::with_vertices(4);
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert_eq!(roots, vec![0, 1, 2, 3]);
}
#[test]
fn no_edges_directed_out_every_vertex_is_a_root() {
let g = Graph::new(3, true).unwrap();
let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
assert_eq!(roots, vec![0, 1, 2]);
}
#[test]
fn undirected_path_is_forest_one_root() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert_eq!(roots, vec![0]);
}
#[test]
fn undirected_two_components_each_a_tree() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0u32, 1u32), (2, 3), (3, 4)]).unwrap();
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert_eq!(roots, vec![0, 2]);
}
#[test]
fn undirected_triangle_is_not_a_forest() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn undirected_self_loop_breaks_forest() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(1, 2).unwrap();
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn undirected_parallel_edges_break_forest() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn directed_out_two_arborescences_are_a_forest() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (2, 3)]).unwrap();
let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
assert_eq!(roots, vec![0, 2]);
}
#[test]
fn directed_out_v_pattern_is_not_an_out_forest() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
}
#[test]
fn directed_in_two_anti_arborescences_are_a_forest() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(1u32, 0u32), (3, 2)]).unwrap();
let roots = is_forest(&g, DijkstraMode::In).unwrap().unwrap();
assert_eq!(roots, vec![0, 2]);
}
#[test]
fn directed_cycle_is_not_a_forest_in_any_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
assert!(is_forest(&g, DijkstraMode::In).unwrap().is_none());
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn directed_all_mode_treats_as_undirected() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert_eq!(roots, vec![0]);
}
#[test]
fn too_many_edges_is_not_a_forest() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3), (3, 4), (4, 0)])
.unwrap();
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn correct_edge_count_but_disconnected_with_cycle_is_not_a_forest() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
}
#[test]
fn single_tree_is_also_a_forest() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3)]).unwrap();
let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
assert_eq!(roots, vec![0]);
}
#[test]
fn single_out_arborescence_is_also_an_out_forest() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3)]).unwrap();
let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
assert_eq!(roots, vec![0]);
}
}