pub fn tarjan_scc<'a>(node_count: usize, successors: impl Fn(u32) -> &'a [u32]) -> Vec<Vec<u32>> {
if node_count == 0 {
return Vec::new();
}
let mut index = vec![u32::MAX; node_count];
let mut lowlink = vec![0u32; node_count];
let mut on_stack = vec![false; node_count];
let mut stack = Vec::new();
let mut sccs = Vec::new();
let mut current_index = 0u32;
let mut call_stack: Vec<(u32, usize)> = Vec::new();
for root in 0..node_count as u32 {
if index[root as usize] != u32::MAX {
continue;
}
call_stack.push((root, 0));
index[root as usize] = current_index;
lowlink[root as usize] = current_index;
current_index += 1;
on_stack[root as usize] = true;
stack.push(root);
while let Some(&mut (v, ref mut si)) = call_stack.last_mut() {
let succs = successors(v);
if *si < succs.len() {
let w = succs[*si];
*si += 1;
if (w as usize) >= node_count {
continue;
}
if index[w as usize] == u32::MAX {
index[w as usize] = current_index;
lowlink[w as usize] = current_index;
current_index += 1;
on_stack[w as usize] = true;
stack.push(w);
call_stack.push((w, 0));
} else if on_stack[w as usize] {
lowlink[v as usize] = lowlink[v as usize].min(index[w as usize]);
}
} else {
if lowlink[v as usize] == index[v as usize] {
let mut scc = Vec::new();
loop {
let w = stack.pop().expect("stack underflow in tarjan");
on_stack[w as usize] = false;
scc.push(w);
if w == v {
break;
}
}
sccs.push(scc);
}
call_stack.pop();
if let Some(&(parent, _)) = call_stack.last() {
lowlink[parent as usize] = lowlink[parent as usize].min(lowlink[v as usize]);
}
}
}
}
sccs
}
pub fn compute_dominators<'a>(
node_count: usize,
root: u32,
successors: impl Fn(u32) -> &'a [u32],
predecessors: impl Fn(u32) -> &'a [u32],
) -> Vec<Option<u32>> {
if node_count == 0 {
return Vec::new();
}
let mut rpo = Vec::with_capacity(node_count);
let mut rpo_number = vec![u32::MAX; node_count]; {
let mut visited = vec![false; node_count];
let mut dfs_stack: Vec<(u32, usize)> = Vec::new();
visited[root as usize] = true;
dfs_stack.push((root, 0));
while let Some(&mut (v, ref mut si)) = dfs_stack.last_mut() {
let succs = successors(v);
if *si < succs.len() {
let w = succs[*si];
*si += 1;
if (w as usize) < node_count && !visited[w as usize] {
visited[w as usize] = true;
dfs_stack.push((w, 0));
}
} else {
dfs_stack.pop();
rpo_number[v as usize] = rpo.len() as u32;
rpo.push(v);
}
}
rpo.reverse();
for (i, &v) in rpo.iter().enumerate() {
rpo_number[v as usize] = i as u32;
}
}
let mut idom: Vec<Option<u32>> = vec![None; node_count];
idom[root as usize] = Some(root);
let intersect = |mut a: u32, mut b: u32, idom: &[Option<u32>]| -> u32 {
while a != b {
while rpo_number[a as usize] > rpo_number[b as usize] {
a = idom[a as usize].expect("intersect: undefined idom");
}
while rpo_number[b as usize] > rpo_number[a as usize] {
b = idom[b as usize].expect("intersect: undefined idom");
}
}
a
};
let mut changed = true;
while changed {
changed = false;
for &v in &rpo {
if v == root {
continue;
}
let preds = predecessors(v);
let mut new_idom = None;
for &p in preds {
if idom[p as usize].is_some() {
new_idom = Some(p);
break;
}
}
let Some(mut new_idom_val) = new_idom else {
continue;
};
for &p in preds {
if p == new_idom_val {
continue;
}
if idom[p as usize].is_some() {
new_idom_val = intersect(p, new_idom_val, &idom);
}
}
if idom[v as usize] != Some(new_idom_val) {
idom[v as usize] = Some(new_idom_val);
changed = true;
}
}
}
idom[root as usize] = None;
idom
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tarjan_no_cycles() {
let adj: Vec<Vec<u32>> = vec![vec![1], vec![2], vec![]];
let sccs = tarjan_scc(3, |v| &adj[v as usize]);
assert_eq!(sccs.len(), 3);
assert!(sccs.iter().all(|scc| scc.len() == 1));
}
#[test]
fn test_tarjan_single_cycle() {
let adj: Vec<Vec<u32>> = vec![vec![1], vec![2], vec![0]];
let sccs = tarjan_scc(3, |v| &adj[v as usize]);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 3);
}
#[test]
fn test_tarjan_mixed() {
let adj: Vec<Vec<u32>> = vec![vec![1, 3], vec![2], vec![1], vec![]];
let sccs = tarjan_scc(4, |v| &adj[v as usize]);
let big: Vec<_> = sccs.iter().filter(|s| s.len() > 1).collect();
assert_eq!(big.len(), 1);
assert_eq!(big[0].len(), 2);
}
#[test]
fn test_tarjan_empty() {
let sccs = tarjan_scc(0, |_| &[]);
assert!(sccs.is_empty());
}
#[test]
fn test_dominators_linear() {
let fwd: Vec<Vec<u32>> = vec![vec![1], vec![2], vec![3], vec![]];
let rev: Vec<Vec<u32>> = vec![vec![], vec![0], vec![1], vec![2]];
let idom = compute_dominators(4, 0, |v| &fwd[v as usize], |v| &rev[v as usize]);
assert_eq!(idom[0], None); assert_eq!(idom[1], Some(0));
assert_eq!(idom[2], Some(1));
assert_eq!(idom[3], Some(2));
}
#[test]
fn test_dominators_diamond() {
let fwd: Vec<Vec<u32>> = vec![vec![1, 2], vec![3], vec![3], vec![]];
let rev: Vec<Vec<u32>> = vec![vec![], vec![0], vec![0], vec![1, 2]];
let idom = compute_dominators(4, 0, |v| &fwd[v as usize], |v| &rev[v as usize]);
assert_eq!(idom[0], None);
assert_eq!(idom[1], Some(0));
assert_eq!(idom[2], Some(0));
assert_eq!(idom[3], Some(0)); }
#[test]
fn test_dominators_unreachable() {
let fwd: Vec<Vec<u32>> = vec![vec![1], vec![], vec![]];
let rev: Vec<Vec<u32>> = vec![vec![], vec![0], vec![]];
let idom = compute_dominators(3, 0, |v| &fwd[v as usize], |v| &rev[v as usize]);
assert_eq!(idom[0], None);
assert_eq!(idom[1], Some(0));
assert_eq!(idom[2], None); }
}