graph_advanced/
kosaraju_scc.rs

1use graph_core::{Graph, NodeId};
2use std::collections::{HashMap, HashSet};
3
4/// Finds all **Strongly Connected Components** (SCCs) using Kosaraju's algorithm.
5///
6/// Kosaraju's algorithm uses two DFS passes:
7/// 1. Run DFS on the **original** graph, recording nodes in finish order.
8/// 2. Run DFS on the **transposed** graph in reverse finish order — each DFS
9///    tree in this pass is one SCC.
10///
11/// # Returns
12///
13/// A `Vec` of SCCs, each SCC being a `Vec<NodeId>`. SCCs are returned in
14/// **topological order** of the condensed DAG (the first SCC has no incoming
15/// edges from other SCCs in the condensed graph).
16///
17/// # Complexity
18///
19/// O(V + E) — two DFS passes plus one graph transposition.
20///
21/// # Examples
22///
23/// ```
24/// use graph_core::{AdjacencyList, Graph};
25/// use graph_advanced::kosaraju_scc;
26///
27/// // 0 → 1 → 0  (cycle),  1 → 2  (singleton)
28/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
29/// let n: Vec<_> = (0..3).map(|_| g.add_node(())).collect();
30/// g.add_edge(n[0], n[1], 1.0).unwrap();
31/// g.add_edge(n[1], n[0], 1.0).unwrap();
32/// g.add_edge(n[1], n[2], 1.0).unwrap();
33///
34/// let sccs = kosaraju_scc(&g);
35/// assert_eq!(sccs.len(), 2);
36/// ```
37pub fn kosaraju_scc<G>(graph: &G) -> Vec<Vec<NodeId>>
38where
39    G: Graph<Weight = f64>,
40{
41    // ── Pass 1: DFS on original graph, record finish order ───────────────────
42    let mut visited: HashSet<NodeId> = HashSet::new();
43    let mut finish_order: Vec<NodeId> = Vec::new();
44
45    for node in graph.nodes() {
46        if !visited.contains(&node) {
47            dfs_finish(graph, node, &mut visited, &mut finish_order);
48        }
49    }
50
51    // ── Build transposed graph ────────────────────────────────────────────────
52    // Map each NodeId to a dense index for the transpose adjacency list.
53    let node_list: Vec<NodeId> = graph.nodes().collect();
54    let index_of: HashMap<NodeId, usize> = node_list
55        .iter()
56        .enumerate()
57        .map(|(i, &id)| (id, i))
58        .collect();
59    let n = node_list.len();
60
61    // transpose[i] = list of nodes that have an edge TO node_list[i].
62    let mut transpose: Vec<Vec<usize>> = vec![Vec::new(); n];
63    for &u in &node_list {
64        for (v, _) in graph.neighbors(u) {
65            let ui = index_of[&u];
66            let vi = index_of[&v];
67            transpose[vi].push(ui); // edge u→v in original becomes v→u in transpose
68        }
69    }
70
71    // ── Pass 2: DFS on transposed graph in reverse finish order ──────────────
72    let mut visited2: HashSet<usize> = HashSet::new();
73    let mut result: Vec<Vec<NodeId>> = Vec::new();
74
75    for node in finish_order.into_iter().rev() {
76        let idx = index_of[&node];
77        if !visited2.contains(&idx) {
78            let mut scc_indices: Vec<usize> = Vec::new();
79            dfs_transpose(idx, &transpose, &mut visited2, &mut scc_indices);
80            let scc = scc_indices.iter().map(|&i| node_list[i]).collect();
81            result.push(scc);
82        }
83    }
84
85    result
86}
87
88/// DFS on the original graph; appends nodes to `finish_order` in post-order.
89fn dfs_finish<G>(
90    graph: &G,
91    node: NodeId,
92    visited: &mut HashSet<NodeId>,
93    finish_order: &mut Vec<NodeId>,
94) where
95    G: Graph<Weight = f64>,
96{
97    if !visited.insert(node) {
98        return;
99    }
100    for (neighbour, _) in graph.neighbors(node) {
101        dfs_finish(graph, neighbour, visited, finish_order);
102    }
103    finish_order.push(node);
104}
105
106/// DFS on the transposed graph (stored as index-based adjacency list).
107fn dfs_transpose(
108    node: usize,
109    transpose: &[Vec<usize>],
110    visited: &mut HashSet<usize>,
111    scc: &mut Vec<usize>,
112) {
113    if !visited.insert(node) {
114        return;
115    }
116    scc.push(node);
117    for &neighbour in &transpose[node] {
118        dfs_transpose(neighbour, transpose, visited, scc);
119    }
120}