use std::collections::VecDeque;
use crate::connectivity::scc_tarjan::scc_tarjan;
use crate::dynamic::dynamic_graph::DynamicGraph;
use crate::error::{GraphalgError, GraphalgResult};
#[derive(Debug, Clone)]
pub struct IncrementalScc {
graph: DynamicGraph,
comp_of: Vec<usize>,
members: Vec<Vec<usize>>,
}
impl IncrementalScc {
pub fn new(graph: DynamicGraph) -> GraphalgResult<Self> {
let n = graph.num_nodes();
let mut me = Self {
graph,
comp_of: vec![0; n],
members: Vec::new(),
};
me.full_recompute()?;
Ok(me)
}
pub fn num_nodes(&self) -> usize {
self.graph.num_nodes()
}
pub fn num_components(&self) -> usize {
self.members.len()
}
pub fn component_of(&self, v: usize) -> GraphalgResult<usize> {
if v >= self.graph.num_nodes() {
return Err(GraphalgError::IndexOutOfBounds {
index: v,
len: self.graph.num_nodes(),
});
}
Ok(self.comp_of[v])
}
pub fn same_component(&self, u: usize, v: usize) -> GraphalgResult<bool> {
Ok(self.component_of(u)? == self.component_of(v)?)
}
pub fn components(&self) -> Vec<Vec<usize>> {
let mut out: Vec<Vec<usize>> = self
.members
.iter()
.map(|m| {
let mut v = m.clone();
v.sort_unstable();
v
})
.collect();
out.sort_by_key(|c| c.first().copied().unwrap_or(usize::MAX));
out
}
pub fn graph(&self) -> &DynamicGraph {
&self.graph
}
pub fn apply_insert(&mut self, u: usize, v: usize) -> GraphalgResult<()> {
self.graph.add_edge(u, v)?;
self.handle_insert(u, v)
}
pub fn apply_remove(&mut self, u: usize, v: usize) -> GraphalgResult<()> {
self.graph.remove_edge(u, v)?;
self.handle_remove(u, v)
}
pub fn apply_batch(&mut self, updates: &[(usize, usize, bool)]) -> GraphalgResult<()> {
for &(u, v, is_insert) in updates {
if is_insert {
self.apply_insert(u, v)?;
} else {
self.apply_remove(u, v)?;
}
}
Ok(())
}
fn full_recompute(&mut self) -> GraphalgResult<()> {
let g = self.graph.to_adjacency_list();
let sccs = scc_tarjan(&g)?;
self.install_partition(sccs);
Ok(())
}
fn install_partition(&mut self, sccs: Vec<Vec<usize>>) {
let n = self.graph.num_nodes();
let mut comp_of = vec![usize::MAX; n];
let mut members: Vec<Vec<usize>> = Vec::with_capacity(sccs.len());
for comp in sccs {
let id = members.len();
for &v in &comp {
comp_of[v] = id;
}
members.push(comp);
}
self.comp_of = comp_of;
self.members = members;
}
fn handle_insert(&mut self, u: usize, v: usize) -> GraphalgResult<()> {
let cu = self.comp_of[u];
let cv = self.comp_of[v];
if cu == cv {
return Ok(());
}
let merge = self.condensation_cycle_components(cv, cu);
if merge.len() <= 1 {
return Ok(());
}
self.merge_components(&merge);
Ok(())
}
fn handle_remove(&mut self, u: usize, v: usize) -> GraphalgResult<()> {
let n = self.graph.num_nodes();
if u >= n || v >= n {
return Ok(());
}
let cu = self.comp_of[u];
let cv = self.comp_of[v];
if cu != cv {
return Ok(());
}
let component = self.members[cu].clone();
let sub = self.tarjan_on_subset(&component)?;
if sub.len() == 1 {
return Ok(());
}
self.replace_component(cu, sub);
Ok(())
}
fn condensation_cycle_components(&self, start_comp: usize, target_comp: usize) -> Vec<usize> {
let nc = self.members.len();
let forward = self.condensation_reach(start_comp, false);
if !forward[target_comp] {
return Vec::new();
}
let backward = self.condensation_reach(target_comp, true);
let mut out = Vec::new();
for c in 0..nc {
if forward[c] && backward[c] {
out.push(c);
}
}
out
}
fn condensation_reach(&self, src: usize, reverse: bool) -> Vec<bool> {
let nc = self.members.len();
let mut seen = vec![false; nc];
if src >= nc {
return seen;
}
seen[src] = true;
let mut queue = VecDeque::new();
queue.push_back(src);
while let Some(c) = queue.pop_front() {
for &x in &self.members[c] {
let neigh = if reverse {
self.graph.in_neighbors(x)
} else {
self.graph.out_neighbors(x)
};
if let Ok(list) = neigh {
for &y in list {
let cy = self.comp_of[y];
if cy != c && !seen[cy] {
seen[cy] = true;
queue.push_back(cy);
}
}
}
}
}
seen
}
fn merge_components(&mut self, merge: &[usize]) {
let mut keep = vec![true; self.members.len()];
let mut fused: Vec<usize> = Vec::new();
for &c in merge {
keep[c] = false;
fused.extend(std::mem::take(&mut self.members[c]));
}
let mut new_members: Vec<Vec<usize>> = Vec::with_capacity(self.members.len());
for c in 0..self.members.len() {
if keep[c] {
new_members.push(std::mem::take(&mut self.members[c]));
}
}
new_members.push(fused);
self.reinstall(new_members);
}
fn replace_component(&mut self, cid: usize, sub: Vec<Vec<usize>>) {
let mut new_members: Vec<Vec<usize>> = Vec::with_capacity(self.members.len() + sub.len());
for c in 0..self.members.len() {
if c == cid {
continue;
}
new_members.push(std::mem::take(&mut self.members[c]));
}
for s in sub {
new_members.push(s);
}
self.reinstall(new_members);
}
fn reinstall(&mut self, members: Vec<Vec<usize>>) {
let n = self.graph.num_nodes();
let mut comp_of = vec![usize::MAX; n];
for (id, comp) in members.iter().enumerate() {
for &v in comp {
comp_of[v] = id;
}
}
self.comp_of = comp_of;
self.members = members;
}
fn tarjan_on_subset(&self, subset: &[usize]) -> GraphalgResult<Vec<Vec<usize>>> {
let k = subset.len();
let mut local = vec![usize::MAX; self.graph.num_nodes()];
for (i, &v) in subset.iter().enumerate() {
local[v] = i;
}
let mut sub = crate::repr::adjacency_list::AdjacencyList::new(k);
for &v in subset {
let lv = local[v];
for &w in self.graph.out_neighbors(v)? {
let lw = local[w];
if lw != usize::MAX {
sub.add_edge(lv, lw)?;
}
}
}
let local_sccs = scc_tarjan(&sub)?;
let translated = local_sccs
.into_iter()
.map(|comp| comp.into_iter().map(|li| subset[li]).collect::<Vec<_>>())
.collect();
Ok(translated)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handle::LcgRng;
fn canon(mut parts: Vec<Vec<usize>>) -> Vec<Vec<usize>> {
for p in &mut parts {
p.sort_unstable();
}
parts.sort_by_key(|p| p.first().copied().unwrap_or(usize::MAX));
parts
}
fn ground_truth(g: &DynamicGraph) -> Vec<Vec<usize>> {
canon(scc_tarjan(&g.to_adjacency_list()).expect("tarjan"))
}
#[test]
fn insertion_merges_into_cycle() {
let mut g = DynamicGraph::new(3);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 2).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
assert_eq!(scc.num_components(), 3);
scc.apply_insert(2, 0).expect("insert");
assert_eq!(scc.num_components(), 1);
assert!(scc.same_component(0, 2).expect("same"));
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn insertion_without_cycle_keeps_partition() {
let mut g = DynamicGraph::new(4);
g.add_edge(0, 1).expect("e");
g.add_edge(2, 3).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
let before = scc.num_components();
scc.apply_insert(1, 2).expect("insert"); assert_eq!(scc.num_components(), before);
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn deletion_splits_cycle() {
let mut g = DynamicGraph::new(3);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 2).expect("e");
g.add_edge(2, 0).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
assert_eq!(scc.num_components(), 1);
scc.apply_remove(2, 0).expect("remove");
assert_eq!(scc.num_components(), 3);
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn deletion_of_cross_edge_noop() {
let mut g = DynamicGraph::new(4);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 0).expect("e"); g.add_edge(1, 2).expect("e"); g.add_edge(2, 3).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
let before = scc.components();
scc.apply_remove(1, 2).expect("remove");
assert_eq!(scc.components(), before);
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn deletion_keeps_scc_when_alternate_path() {
let mut g = DynamicGraph::new(3);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 2).expect("e");
g.add_edge(1, 2).expect("e"); g.add_edge(2, 0).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
assert_eq!(scc.num_components(), 1);
scc.apply_remove(1, 2).expect("remove"); assert_eq!(scc.num_components(), 1, "parallel edge keeps SCC");
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn merge_of_two_existing_sccs() {
let mut g = DynamicGraph::new(4);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 0).expect("e");
g.add_edge(2, 3).expect("e");
g.add_edge(3, 2).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
assert_eq!(scc.num_components(), 2);
scc.apply_insert(1, 2).expect("insert"); assert_eq!(scc.num_components(), 2);
scc.apply_insert(3, 0).expect("insert"); assert_eq!(scc.num_components(), 1);
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn random_update_stream_matches_tarjan() {
let mut rng = LcgRng::new(0x5EED_1234);
for trial in 0..40 {
let n = 4 + rng.next_usize(7); let mut scc = IncrementalScc::new(DynamicGraph::new(n)).expect("new");
let mut present: Vec<(usize, usize)> = Vec::new();
for step in 0..60 {
let do_insert = present.is_empty() || rng.next_bool();
if do_insert {
let u = rng.next_usize(n);
let mut v = rng.next_usize(n);
if v == u {
v = (v + 1) % n;
}
scc.apply_insert(u, v).expect("insert");
if !present.contains(&(u, v)) {
present.push((u, v));
}
} else {
let idx = rng.next_usize(present.len());
let (u, v) = present[idx];
scc.apply_remove(u, v).expect("remove");
present.swap_remove(idx);
}
assert_eq!(
scc.components(),
ground_truth(scc.graph()),
"mismatch trial {trial} step {step} (n={n})"
);
for v in 0..n {
let cid = scc.component_of(v).expect("comp");
assert!(scc.members[cid].contains(&v));
}
}
}
}
#[test]
fn batch_update_matches_tarjan() {
let mut g = DynamicGraph::new(5);
g.add_edge(0, 1).expect("e");
g.add_edge(1, 2).expect("e");
let mut scc = IncrementalScc::new(g).expect("new");
let updates = [
(2, 0, true), (3, 4, true), (4, 3, true), (2, 3, true), (0, 1, false), ];
scc.apply_batch(&updates).expect("batch");
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn self_loop_is_its_own_scc_member() {
let mut g = DynamicGraph::new(2);
g.add_edge(0, 0).expect("e"); g.add_edge(0, 1).expect("e");
let scc = IncrementalScc::new(g).expect("new");
assert_eq!(scc.num_components(), 2);
assert_eq!(scc.components(), ground_truth(scc.graph()));
}
#[test]
fn component_of_out_of_range_errors() {
let scc = IncrementalScc::new(DynamicGraph::new(2)).expect("new");
assert!(scc.component_of(5).is_err());
}
}