use crate::utils::graph::{NodeId, Successors};
pub fn strongly_connected_components<G>(graph: &G) -> Vec<Vec<NodeId>>
where
G: Successors,
{
let node_count = graph.node_count();
if node_count == 0 {
return Vec::new();
}
let mut state = TarjanState::new(node_count);
for i in 0..node_count {
let node = NodeId::new(i);
if state.index[i].is_none() {
state.strongconnect(graph, node);
}
}
state.sccs
}
struct TarjanState {
index: Vec<Option<usize>>,
lowlink: Vec<usize>,
on_stack: Vec<bool>,
stack: Vec<NodeId>,
current_index: usize,
sccs: Vec<Vec<NodeId>>,
}
impl TarjanState {
fn new(n: usize) -> Self {
Self {
index: vec![None; n],
lowlink: vec![0; n],
on_stack: vec![false; n],
stack: Vec::new(),
current_index: 0,
sccs: Vec::new(),
}
}
fn strongconnect<G: Successors>(&mut self, graph: &G, v: NodeId) {
let v_idx = v.index();
self.index[v_idx] = Some(self.current_index);
self.lowlink[v_idx] = self.current_index;
self.current_index += 1;
self.stack.push(v);
self.on_stack[v_idx] = true;
for w in graph.successors(v) {
let w_idx = w.index();
if self.index[w_idx].is_none() {
self.strongconnect(graph, w);
self.lowlink[v_idx] = self.lowlink[v_idx].min(self.lowlink[w_idx]);
} else if self.on_stack[w_idx] {
if let Some(idx) = self.index[w_idx] {
self.lowlink[v_idx] = self.lowlink[v_idx].min(idx);
}
}
}
if let Some(idx) = self.index[v_idx] {
if self.lowlink[v_idx] == idx {
let mut scc = Vec::new();
while let Some(w) = self.stack.pop() {
self.on_stack[w.index()] = false;
scc.push(w);
if w == v {
break;
}
}
self.sccs.push(scc);
}
}
}
}
pub fn condensation<G>(graph: &G, sccs: &[Vec<NodeId>]) -> (Vec<usize>, Vec<(usize, usize)>)
where
G: Successors,
{
let node_count = graph.node_count();
let mut node_to_scc = vec![0; node_count];
for (scc_idx, scc) in sccs.iter().enumerate() {
for &node in scc {
node_to_scc[node.index()] = scc_idx;
}
}
let mut edges = Vec::new();
let mut seen_edges = std::collections::HashSet::new();
for i in 0..node_count {
let from_node = NodeId::new(i);
let from_scc = node_to_scc[i];
for to_node in graph.successors(from_node) {
let to_scc = node_to_scc[to_node.index()];
if from_scc != to_scc && seen_edges.insert((from_scc, to_scc)) {
edges.push((from_scc, to_scc));
}
}
}
(node_to_scc, edges)
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::utils::graph::{
algorithms::scc::{condensation, strongly_connected_components},
DirectedGraph, NodeId,
};
#[test]
fn test_scc_empty_graph() {
let graph: DirectedGraph<(), ()> = DirectedGraph::new();
let sccs = strongly_connected_components(&graph);
assert!(sccs.is_empty());
}
#[test]
fn test_scc_single_node() {
let mut graph: DirectedGraph<(), ()> = DirectedGraph::new();
let a = graph.add_node(());
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0], vec![a]);
}
#[test]
fn test_scc_single_node_self_loop() {
let mut graph: DirectedGraph<(), ()> = DirectedGraph::new();
let a = graph.add_node(());
graph.add_edge(a, a, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0], vec![a]);
}
#[test]
fn test_scc_linear_chain() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 3);
for scc in &sccs {
assert_eq!(scc.len(), 1);
}
let scc_nodes: Vec<NodeId> = sccs.iter().map(|scc| scc[0]).collect();
assert_eq!(scc_nodes, vec![c, b, a]);
}
#[test]
fn test_scc_simple_cycle() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, a, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 3);
let scc_set: HashSet<NodeId> = sccs[0].iter().copied().collect();
assert!(scc_set.contains(&a));
assert!(scc_set.contains(&b));
assert!(scc_set.contains(&c));
}
#[test]
fn test_scc_two_nodes_cycle() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 2);
}
#[test]
fn test_scc_multiple_components() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 2);
for scc in &sccs {
assert_eq!(scc.len(), 2);
}
}
#[test]
fn test_scc_connected_cycles() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 2);
let mut found_ab = false;
let mut found_cd = false;
for scc in &sccs {
let scc_set: HashSet<NodeId> = scc.iter().copied().collect();
if scc_set.contains(&a) && scc_set.contains(&b) && scc.len() == 2 {
found_ab = true;
}
if scc_set.contains(&c) && scc_set.contains(&d) && scc.len() == 2 {
found_cd = true;
}
}
assert!(found_ab);
assert!(found_cd);
}
#[test]
fn test_scc_diamond_no_cycle() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(a, c, ()).unwrap();
graph.add_edge(b, d, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 4);
for scc in &sccs {
assert_eq!(scc.len(), 1);
}
}
#[test]
fn test_scc_figure_eight() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 2);
}
#[test]
fn test_scc_reverse_topological_order() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
let e = graph.add_node('E');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
graph.add_edge(d, e, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 3);
let find_scc =
|node: NodeId| -> usize { sccs.iter().position(|scc| scc.contains(&node)).unwrap() };
let scc_ab = find_scc(a);
let scc_cd = find_scc(c);
let scc_e = find_scc(e);
assert!(scc_e < scc_cd);
assert!(scc_cd < scc_ab);
}
#[test]
fn test_scc_large_cycle() {
let mut graph: DirectedGraph<usize, ()> = DirectedGraph::new();
let nodes: Vec<NodeId> = (0..100).map(|i| graph.add_node(i)).collect();
for i in 0..100 {
graph.add_edge(nodes[i], nodes[(i + 1) % 100], ()).unwrap();
}
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 100);
}
#[test]
fn test_condensation_basic() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
let (node_to_scc, edges) = condensation(&graph, &sccs);
assert_eq!(node_to_scc[a.index()], node_to_scc[b.index()]);
assert_ne!(node_to_scc[a.index()], node_to_scc[c.index()]);
assert_eq!(edges.len(), 1);
}
#[test]
fn test_condensation_no_edges() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
let sccs = strongly_connected_components(&graph);
let (_, edges) = condensation(&graph, &sccs);
assert!(edges.is_empty());
}
#[test]
fn test_condensation_chain() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
let e = graph.add_node('E');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
graph.add_edge(d, e, ()).unwrap();
let sccs = strongly_connected_components(&graph);
let (node_to_scc, edges) = condensation(&graph, &sccs);
assert_eq!(node_to_scc[a.index()], node_to_scc[b.index()]);
assert_eq!(node_to_scc[c.index()], node_to_scc[d.index()]);
assert_ne!(node_to_scc[a.index()], node_to_scc[c.index()]);
assert_ne!(node_to_scc[c.index()], node_to_scc[e.index()]);
assert_eq!(edges.len(), 2);
}
#[test]
fn test_scc_disconnected_graph() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let _a = graph.add_node('A');
let _b = graph.add_node('B');
let _c = graph.add_node('C');
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 3);
for scc in &sccs {
assert_eq!(scc.len(), 1);
}
}
#[test]
fn test_scc_complex_structure() {
let mut graph: DirectedGraph<char, ()> = DirectedGraph::new();
let a = graph.add_node('A');
let b = graph.add_node('B');
let c = graph.add_node('C');
let d = graph.add_node('D');
let e = graph.add_node('E');
let f = graph.add_node('F');
let g = graph.add_node('G');
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, b, ()).unwrap(); graph.add_edge(a, d, ()).unwrap();
graph.add_edge(b, e, ()).unwrap();
graph.add_edge(d, e, ()).unwrap();
graph.add_edge(e, d, ()).unwrap(); graph.add_edge(e, f, ()).unwrap();
graph.add_edge(f, g, ()).unwrap();
let sccs = strongly_connected_components(&graph);
assert_eq!(sccs.len(), 5);
let mut size_counts = std::collections::HashMap::new();
for scc in &sccs {
*size_counts.entry(scc.len()).or_insert(0) += 1;
}
assert_eq!(size_counts.get(&2), Some(&2));
assert_eq!(size_counts.get(&1), Some(&3));
}
}