use super::Adjacency;
#[derive(Clone, Debug, PartialEq)]
pub struct Components {
pub of: Vec<usize>,
pub count: usize,
}
impl Components {
#[must_use]
pub fn groups(&self) -> Vec<Vec<usize>> {
let mut g = vec![Vec::new(); self.count];
for (i, &c) in self.of.iter().enumerate() {
g[c].push(i);
}
g
}
#[must_use]
pub fn largest(&self) -> usize {
self.groups().iter().map(Vec::len).max().unwrap_or(0)
}
}
#[must_use]
pub fn connected(g: &Adjacency) -> Components {
let n = g.n;
let mut uf = UnionFind::new(n);
for i in 0..n {
for &(j, _) in &g.und[i] {
uf.union(i, j);
}
}
compactify(&(0..n).map(|i| uf.find(i)).collect::<Vec<_>>())
}
#[must_use]
pub fn strongly_connected(g: &Adjacency) -> Components {
let n = g.n;
let mut index = vec![usize::MAX; n];
let mut low = vec![0usize; n];
let mut on_stack = vec![false; n];
let mut stack: Vec<usize> = Vec::new();
let mut comp = vec![usize::MAX; n];
let mut next_index = 0usize;
let mut next_comp = 0usize;
for start in 0..n {
if index[start] != usize::MAX {
continue;
}
let mut work: Vec<(usize, usize)> = vec![(start, 0)];
while let Some(&(v, ci)) = work.last() {
if ci == 0 {
index[v] = next_index;
low[v] = next_index;
next_index += 1;
stack.push(v);
on_stack[v] = true;
}
if ci < g.out[v].len() {
work.last_mut().unwrap().1 += 1;
let w = g.out[v][ci].0;
if index[w] == usize::MAX {
work.push((w, 0));
} else if on_stack[w] {
low[v] = low[v].min(index[w]);
}
} else {
if low[v] == index[v] {
loop {
let w = stack.pop().unwrap();
on_stack[w] = false;
comp[w] = next_comp;
if w == v {
break;
}
}
next_comp += 1;
}
work.pop();
if let Some(&(p, _)) = work.last() {
low[p] = low[p].min(low[v]);
}
}
}
}
Components { of: comp, count: next_comp }
}
fn compactify(roots: &[usize]) -> Components {
let mut map = std::collections::HashMap::new();
let mut of = Vec::with_capacity(roots.len());
let mut count = 0;
for &r in roots {
let id = *map.entry(r).or_insert_with(|| {
let id = count;
count += 1;
id
});
of.push(id);
}
Components { of, count }
}
struct UnionFind {
parent: Vec<usize>,
size: Vec<usize>,
}
impl UnionFind {
fn new(n: usize) -> Self {
Self { parent: (0..n).collect(), size: vec![1; n] }
}
fn find(&mut self, x: usize) -> usize {
let mut r = x;
while self.parent[r] != r {
r = self.parent[r];
}
let mut c = x;
while self.parent[c] != r {
let nxt = self.parent[c];
self.parent[c] = r;
c = nxt;
}
r
}
fn union(&mut self, a: usize, b: usize) {
let (ra, rb) = (self.find(a), self.find(b));
if ra == rb {
return;
}
let (big, small) = if self.size[ra] >= self.size[rb] { (ra, rb) } else { (rb, ra) };
self.parent[small] = big;
self.size[big] += self.size[small];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connected_finds_two_islands() {
let g = Adjacency::from_edges(5, &[(0, 1), (1, 2), (3, 4)]);
let c = connected(&g);
assert_eq!(c.count, 2);
assert_eq!(c.of[0], c.of[2], "0 and 2 are in one component");
assert_ne!(c.of[0], c.of[3], "the islands are distinct");
assert_eq!(c.largest(), 3);
}
#[test]
fn scc_collapses_a_cycle_but_splits_a_chain() {
let g = Adjacency::from_edges(5, &[(0, 1), (1, 2), (2, 0), (3, 4)]);
let scc = strongly_connected(&g);
assert_eq!(scc.of[0], scc.of[1]);
assert_eq!(scc.of[1], scc.of[2], "the 3-cycle is one SCC");
assert_ne!(scc.of[3], scc.of[4], "an acyclic chain is two singleton SCCs");
assert_eq!(scc.count, 3, "one cycle-SCC + two singletons");
}
#[test]
fn scc_of_a_dag_is_all_singletons() {
let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 3), (0, 3)]);
let scc = strongly_connected(&g);
assert_eq!(scc.count, 4, "a DAG has no non-trivial SCC");
}
#[test]
fn empty_graph_has_isolated_components() {
let g = Adjacency::from_edges(3, &[]);
assert_eq!(connected(&g).count, 3);
assert_eq!(strongly_connected(&g).count, 3);
}
}