use crate::algorithms::paths::dijkstra::DijkstraMode;
use crate::core::Graph;
use crate::core::error::IgraphResult;
use crate::core::graph::{EdgeId, VertexId};
pub fn is_tree(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Option<VertexId>> {
let n = graph.vcount();
let m = graph.ecount();
if n == 0 {
return Ok(None);
}
if m as u64 != u64::from(n).saturating_sub(1) {
return Ok(None);
}
if n == 1 {
return Ok(Some(0));
}
let directed = graph.is_directed();
let effective_mode = if directed { mode } else { DijkstraMode::All };
let Some(root) = pick_root(graph, n, effective_mode)? else {
return Ok(None);
};
let visited_count = dfs_count(graph, n, root, effective_mode, directed)?;
if visited_count == n {
Ok(Some(root))
} else {
Ok(None)
}
}
fn pick_root(graph: &Graph, n: VertexId, mode: DijkstraMode) -> IgraphResult<Option<VertexId>> {
match mode {
DijkstraMode::All => Ok(Some(0)),
DijkstraMode::Out => find_zero_degree_root(graph, n, true),
DijkstraMode::In => find_zero_degree_root(graph, n, false),
}
}
fn find_zero_degree_root(
graph: &Graph,
n: VertexId,
use_in_degree: bool,
) -> IgraphResult<Option<VertexId>> {
for v in 0..n {
let d = if use_in_degree {
graph.incident_in(v)?.len()
} else {
graph.incident(v)?.len()
};
if d == 0 {
return Ok(Some(v));
}
if d > 1 {
return Ok(None);
}
}
Ok(None)
}
fn dfs_count(
graph: &Graph,
n: VertexId,
root: VertexId,
mode: DijkstraMode,
directed: bool,
) -> IgraphResult<u32> {
let n_us = n as usize;
let mut visited: Vec<bool> = vec![false; n_us];
let mut stack: Vec<VertexId> = Vec::with_capacity(n_us);
let mut count: u32 = 0;
stack.push(root);
while let Some(u) = stack.pop() {
if visited[u as usize] {
continue;
}
visited[u as usize] = true;
count = count.saturating_add(1);
for eid in incident_edges(graph, u, mode, directed)? {
let nei = graph.edge_other(eid, u)?;
if !visited[nei as usize] {
stack.push(nei);
}
}
}
Ok(count)
}
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_not_a_tree() {
let g = Graph::with_vertices(0);
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn single_vertex_undirected_is_a_tree() {
let g = Graph::with_vertices(1);
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
}
#[test]
fn single_vertex_directed_out_is_a_tree() {
let g = Graph::new(1, true).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
}
#[test]
fn undirected_path_is_a_tree() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
}
#[test]
fn undirected_star_is_a_tree() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (0, 4)])
.unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
}
#[test]
fn undirected_triangle_is_not_a_tree() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn undirected_disconnected_is_not_a_tree() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0u32, 1u32), (2, 3)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn undirected_disconnected_correct_edge_count_is_not_a_tree() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn undirected_self_loop_breaks_edge_count() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn directed_out_arborescence_is_a_tree() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
}
#[test]
fn directed_out_chain_is_a_tree() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
}
#[test]
fn directed_in_arborescence_is_not_an_out_tree() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(1u32, 0u32), (2, 0), (3, 1)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
assert_eq!(is_tree(&g, DijkstraMode::In).unwrap(), Some(0));
}
#[test]
fn directed_v_pattern_to_center_is_not_an_out_tree() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
}
#[test]
fn directed_cycle_is_not_a_tree() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
}
#[test]
fn directed_all_mode_treats_as_undirected() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (2, 1), (2, 3)]).unwrap();
assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
}
}