use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BiconnectedComponents {
pub count: u32,
pub components: Vec<Vec<VertexId>>,
pub tree_edges: Vec<Vec<EdgeId>>,
pub component_edges: Vec<Vec<EdgeId>>,
pub articulation_points: Vec<VertexId>,
}
#[allow(clippy::too_many_lines)] pub fn biconnected_components(graph: &Graph) -> IgraphResult<BiconnectedComponents> {
let n = graph.vcount();
let mut out = BiconnectedComponents {
count: 0,
components: Vec::new(),
tree_edges: Vec::new(),
component_edges: Vec::new(),
articulation_points: Vec::new(),
};
if n == 0 {
return Ok(out);
}
let n_us = n as usize;
let mut num: Vec<u32> = vec![0; n_us];
let mut low: Vec<u32> = vec![0; n_us];
let mut nextptr: Vec<usize> = vec![0; n_us];
let mut found: Vec<bool> = vec![false; n_us];
let mut path: Vec<VertexId> = Vec::new();
let mut edgestack: Vec<EdgeId> = Vec::new();
let mut counter: u32 = 1;
let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
for v in 0..n {
inc.push(graph.incident(v)?);
}
let mut comps_so_far: u32 = 0;
let mut vertex_added: Vec<u32> = vec![0; n_us];
for i in 0..n {
if low[i as usize] != 0 {
continue;
}
path.push(i);
let mut rootdfs: u32 = 0;
num[i as usize] = counter;
low[i as usize] = counter;
counter = counter
.checked_add(1)
.ok_or(IgraphError::Internal("DFS counter overflow"))?;
while let Some(&act) = path.last() {
let act_us = act as usize;
let nbrs = &inc[act_us];
let actnext = nextptr[act_us];
if actnext < nbrs.len() {
let edge = nbrs[actnext];
let nei = graph.edge_other(edge, act)?;
if low[nei as usize] == 0 {
if act == i {
rootdfs = rootdfs.saturating_add(1);
}
edgestack.push(edge);
path.push(nei);
num[nei as usize] = counter;
low[nei as usize] = counter;
counter = counter
.checked_add(1)
.ok_or(IgraphError::Internal("DFS counter overflow"))?;
} else if num[nei as usize] < low[act_us] {
low[act_us] = num[nei as usize];
}
nextptr[act_us] += 1;
} else {
path.pop();
if let Some(&prev) = path.last() {
let prev_us = prev as usize;
if low[act_us] < low[prev_us] {
low[prev_us] = low[act_us];
}
if low[act_us] >= num[prev_us] {
if !found[prev_us] && prev != i {
out.articulation_points.push(prev);
found[prev_us] = true;
}
out.count = out
.count
.checked_add(1)
.ok_or(IgraphError::Internal("component count overflow"))?;
comps_so_far = comps_so_far
.checked_add(1)
.ok_or(IgraphError::Internal("component count overflow"))?;
let mut comp_tree_edges: Vec<EdgeId> = Vec::new();
let mut comp_vertices: Vec<VertexId> = Vec::new();
while let Some(e) = edgestack.pop() {
let (from, to) = graph.edge(e)?;
comp_tree_edges.push(e);
if vertex_added[from as usize] != comps_so_far {
vertex_added[from as usize] = comps_so_far;
comp_vertices.push(from);
}
if vertex_added[to as usize] != comps_so_far {
vertex_added[to as usize] = comps_so_far;
comp_vertices.push(to);
}
if from == prev || to == prev {
break;
}
}
let mut comp_edges: Vec<EdgeId> = Vec::new();
for &vert in &comp_vertices {
for &e in &inc[vert as usize] {
let nei = graph.edge_other(e, vert)?;
if vertex_added[nei as usize] == comps_so_far && nei < vert {
comp_edges.push(e);
}
}
}
out.components.push(comp_vertices);
out.tree_edges.push(comp_tree_edges);
out.component_edges.push(comp_edges);
}
}
}
}
if rootdfs >= 2 {
out.articulation_points.push(i);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted<T: Ord + Clone>(slice: &[T]) -> Vec<T> {
let mut v = slice.to_vec();
v.sort_unstable();
v
}
#[test]
fn empty_graph_yields_empty() {
let g = Graph::with_vertices(0);
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 0);
assert!(bc.components.is_empty());
assert!(bc.tree_edges.is_empty());
assert!(bc.component_edges.is_empty());
assert!(bc.articulation_points.is_empty());
}
#[test]
fn isolated_vertices_yield_zero_components() {
let g = Graph::with_vertices(5);
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 0);
assert!(bc.components.is_empty());
assert!(bc.component_edges.is_empty());
}
#[test]
fn two_vertices_one_edge_is_one_component() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 1);
assert_eq!(sorted(&bc.components[0]), vec![0, 1]);
assert_eq!(bc.tree_edges[0], vec![0]);
assert_eq!(bc.component_edges[0], vec![0]);
assert!(bc.articulation_points.is_empty());
}
#[test]
fn triangle_is_single_biconnected_component() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 1);
assert_eq!(sorted(&bc.components[0]), vec![0, 1, 2]);
assert_eq!(sorted(&bc.component_edges[0]), vec![0, 1, 2]);
assert_eq!(bc.tree_edges[0].len(), 2);
assert!(bc.articulation_points.is_empty());
}
#[test]
fn k4_complete_component_edges_has_all_six_edges() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 1);
assert_eq!(bc.component_edges[0].len(), 6);
assert_eq!(bc.tree_edges[0].len(), 3);
let mut all = bc.component_edges[0].clone();
all.sort_unstable();
assert_eq!(all, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn pendant_component_edges_match_tree() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 0).unwrap(); g.add_edge(2, 3).unwrap(); let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 2);
let bridge_idx = bc
.components
.iter()
.position(|c| c.len() == 2)
.expect("bridge component");
assert_eq!(bc.tree_edges[bridge_idx], bc.component_edges[bridge_idx]);
let cycle_idx = 1 - bridge_idx;
assert_eq!(bc.component_edges[cycle_idx].len(), 3);
assert_eq!(bc.tree_edges[cycle_idx].len(), 2);
}
#[test]
fn component_edges_partition_non_bridge_edges() {
let mut g = Graph::with_vertices(7);
for &(u, v) in &[
(0u32, 1),
(1, 2),
(2, 0),
(2, 3),
(3, 4),
(4, 5),
(5, 3),
(0, 6),
] {
g.add_edge(u, v).unwrap();
}
let bc = biconnected_components(&g).unwrap();
let total: usize = bc.component_edges.iter().map(Vec::len).sum();
assert_eq!(total, g.ecount());
let mut all: Vec<_> = bc.component_edges.iter().flatten().copied().collect();
all.sort_unstable();
let mut deduped = all.clone();
deduped.dedup();
assert_eq!(all, deduped);
}
#[test]
fn cycle_with_pendant_three_components() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 3);
let mut aps = bc.articulation_points.clone();
aps.sort_unstable();
assert_eq!(aps, vec![2, 3]);
let total_vertices: usize = bc.components.iter().map(Vec::len).sum();
assert_eq!(total_vertices, 7);
}
#[test]
fn star_4_three_components() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 3);
assert_eq!(bc.articulation_points, vec![0]);
for comp in &bc.components {
assert_eq!(comp.len(), 2);
}
}
#[test]
fn upstream_test_fixture_produces_4_components() {
let mut g = Graph::with_vertices(10);
for &(u, v) in &[
(0u32, 1),
(1, 2),
(2, 3),
(3, 0),
(2, 4),
(4, 5),
(5, 2),
(5, 6),
(7, 8),
] {
g.add_edge(u, v).unwrap();
}
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 4);
let mut aps = bc.articulation_points.clone();
aps.sort_unstable();
assert_eq!(aps, vec![2, 5]);
}
#[test]
fn k4_complete_one_component() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
let bc = biconnected_components(&g).unwrap();
assert_eq!(bc.count, 1);
assert_eq!(sorted(&bc.components[0]), vec![0, 1, 2, 3]);
assert!(bc.articulation_points.is_empty());
}
#[test]
fn aps_match_articulation_points_function() {
let mut g = Graph::with_vertices(7);
for &(u, v) in &[
(0u32, 1),
(1, 2),
(2, 0),
(2, 3),
(3, 4),
(4, 5),
(5, 3),
(0, 6),
] {
g.add_edge(u, v).unwrap();
}
let bc = biconnected_components(&g).unwrap();
let mut bc_aps = bc.articulation_points.clone();
bc_aps.sort_unstable();
let mut ap = super::super::articulation::articulation_points(&g).unwrap();
ap.sort_unstable();
assert_eq!(bc_aps, ap);
}
}