use std::collections::VecDeque;
use crate::core::{Graph, IgraphResult, VertexId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectedComponents {
pub membership: Vec<u32>,
pub count: u32,
}
pub fn connected_components(graph: &Graph) -> IgraphResult<ConnectedComponents> {
let n = graph.vcount();
if n == 0 {
return Ok(ConnectedComponents {
membership: Vec::new(),
count: 0,
});
}
let mut membership = vec![u32::MAX; n as usize];
let mut count: u32 = 0;
let mut queue: VecDeque<VertexId> = VecDeque::new();
for start in 0..n {
if membership[start as usize] != u32::MAX {
continue;
}
let component_id = count;
count = count
.checked_add(1)
.ok_or(crate::core::IgraphError::Internal("too many components"))?;
membership[start as usize] = component_id;
queue.clear();
queue.push_back(start);
while let Some(v) = queue.pop_front() {
for w in graph.neighbors(v)? {
if membership[w as usize] == u32::MAX {
membership[w as usize] = component_id;
queue.push_back(w);
}
}
}
}
Ok(ConnectedComponents { membership, count })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph() {
let g = Graph::with_vertices(0);
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 0);
assert!(cc.membership.is_empty());
}
#[test]
fn n_isolated_vertices_yields_n_components() {
let g = Graph::with_vertices(5);
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 5);
assert_eq!(cc.membership, vec![0, 1, 2, 3, 4]);
}
#[test]
fn single_path_is_one_component() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 1);
assert_eq!(cc.membership, vec![0, 0, 0, 0]);
}
#[test]
fn two_disjoint_components_are_distinct() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 2);
assert_eq!(cc.membership, vec![0, 0, 0, 1, 1]);
}
#[test]
fn self_loop_does_not_split_component() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 1);
assert_eq!(cc.membership, vec![0, 0, 0]);
}
#[test]
fn directed_graph_uses_weak_connectivity() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 1);
assert_eq!(cc.membership, vec![0, 0, 0]);
}
#[test]
fn membership_ids_are_dense() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(4, 5).unwrap();
let cc = connected_components(&g).unwrap();
assert_eq!(cc.count, 3);
let mut seen: Vec<u32> = cc.membership.clone();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen, vec![0, 1, 2]);
}
}