Skip to main content

oxihuman_core/
strongly_connected.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Tarjan's strongly connected components (SCC) algorithm.
6
7/// A directed graph for SCC computation.
8pub struct SccGraph {
9    pub n: usize,
10    pub adj: Vec<Vec<usize>>,
11}
12
13impl SccGraph {
14    pub fn new(n: usize) -> Self {
15        SccGraph {
16            n,
17            adj: vec![Vec::new(); n],
18        }
19    }
20}
21
22/// Create a new SCC graph with `n` vertices (0..n).
23pub fn new_scc_graph(n: usize) -> SccGraph {
24    SccGraph::new(n)
25}
26
27/// Add a directed edge from `u` to `v`.
28pub fn scc_add_edge(g: &mut SccGraph, u: usize, v: usize) {
29    if u < g.n && v < g.n {
30        g.adj[u].push(v);
31    }
32}
33
34/// Run Tarjan's SCC. Returns a list of SCCs, each a sorted Vec of vertex indices.
35pub fn tarjan_scc(g: &SccGraph) -> Vec<Vec<usize>> {
36    let n = g.n;
37    let mut index = vec![usize::MAX; n];
38    let mut lowlink = vec![0usize; n];
39    let mut on_stack = vec![false; n];
40    let mut stack: Vec<usize> = Vec::new();
41    let mut sccs: Vec<Vec<usize>> = Vec::new();
42    let mut counter = 0usize;
43
44    for start in 0..n {
45        if index[start] == usize::MAX {
46            /* iterative DFS to avoid stack overflow */
47            let mut dfs_stack: Vec<(usize, usize)> = vec![(start, 0)];
48            index[start] = counter;
49            lowlink[start] = counter;
50            counter += 1;
51            on_stack[start] = true;
52            stack.push(start);
53
54            while let Some((v, ei)) = dfs_stack.last_mut() {
55                let v = *v;
56                let neighbors = &g.adj[v];
57                if *ei < neighbors.len() {
58                    let w = neighbors[*ei];
59                    *ei += 1;
60                    if index[w] == usize::MAX {
61                        index[w] = counter;
62                        lowlink[w] = counter;
63                        counter += 1;
64                        on_stack[w] = true;
65                        stack.push(w);
66                        dfs_stack.push((w, 0));
67                    } else if on_stack[w] {
68                        let lv = lowlink[v];
69                        lowlink[v] = lv.min(index[w]);
70                    }
71                } else {
72                    /* finished v */
73                    dfs_stack.pop();
74                    if let Some(&(parent, _)) = dfs_stack.last() {
75                        let lv = lowlink[parent];
76                        lowlink[parent] = lv.min(lowlink[v]);
77                    }
78                    if lowlink[v] == index[v] {
79                        /* root of SCC */
80                        let mut scc = Vec::new();
81                        #[allow(clippy::while_let_loop)]
82                        loop {
83                            let Some(w) = stack.pop() else { break };
84                            on_stack[w] = false;
85                            scc.push(w);
86                            if w == v {
87                                break;
88                            }
89                        }
90                        scc.sort_unstable();
91                        sccs.push(scc);
92                    }
93                }
94            }
95        }
96    }
97    sccs
98}
99
100/// Return the number of SCCs.
101pub fn scc_count(g: &SccGraph) -> usize {
102    tarjan_scc(g).len()
103}
104
105/// Return `true` if the graph is strongly connected (single SCC with all nodes).
106pub fn is_strongly_connected(g: &SccGraph) -> bool {
107    if g.n == 0 {
108        return true;
109    }
110    let sccs = tarjan_scc(g);
111    sccs.len() == 1
112}
113
114/// Return the largest SCC by node count.
115pub fn largest_scc(g: &SccGraph) -> Vec<usize> {
116    tarjan_scc(g)
117        .into_iter()
118        .max_by_key(|s| s.len())
119        .unwrap_or_default()
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_single_node_scc() {
128        /* a single node is its own SCC */
129        let g = new_scc_graph(1);
130        let sccs = tarjan_scc(&g);
131        assert_eq!(sccs.len(), 1);
132        assert_eq!(sccs[0], vec![0]);
133    }
134
135    #[test]
136    fn test_two_node_cycle() {
137        /* 0 <-> 1 forms one SCC */
138        let mut g = new_scc_graph(2);
139        scc_add_edge(&mut g, 0, 1);
140        scc_add_edge(&mut g, 1, 0);
141        assert_eq!(scc_count(&g), 1);
142    }
143
144    #[test]
145    fn test_dag_three_sccs() {
146        /* 0 -> 1 -> 2; no back edges */
147        let mut g = new_scc_graph(3);
148        scc_add_edge(&mut g, 0, 1);
149        scc_add_edge(&mut g, 1, 2);
150        assert_eq!(scc_count(&g), 3);
151    }
152
153    #[test]
154    fn test_strongly_connected_true() {
155        let mut g = new_scc_graph(3);
156        scc_add_edge(&mut g, 0, 1);
157        scc_add_edge(&mut g, 1, 2);
158        scc_add_edge(&mut g, 2, 0);
159        assert!(is_strongly_connected(&g));
160    }
161
162    #[test]
163    fn test_strongly_connected_false() {
164        let mut g = new_scc_graph(3);
165        scc_add_edge(&mut g, 0, 1);
166        scc_add_edge(&mut g, 1, 2);
167        assert!(!is_strongly_connected(&g));
168    }
169
170    #[test]
171    fn test_empty_graph() {
172        let g = new_scc_graph(0);
173        assert!(is_strongly_connected(&g));
174    }
175
176    #[test]
177    fn test_largest_scc() {
178        /* SCC {0,1,2} and singleton {3} */
179        let mut g = new_scc_graph(4);
180        scc_add_edge(&mut g, 0, 1);
181        scc_add_edge(&mut g, 1, 2);
182        scc_add_edge(&mut g, 2, 0);
183        scc_add_edge(&mut g, 2, 3);
184        let big = largest_scc(&g);
185        assert_eq!(big.len(), 3);
186    }
187
188    #[test]
189    fn test_self_loop_scc() {
190        let mut g = new_scc_graph(2);
191        scc_add_edge(&mut g, 0, 0); /* self-loop */
192        scc_add_edge(&mut g, 1, 1);
193        assert_eq!(scc_count(&g), 2);
194    }
195
196    #[test]
197    fn test_complex_graph() {
198        /* Classic Tarjan example */
199        let mut g = new_scc_graph(8);
200        let edges = [
201            (0, 1),
202            (1, 2),
203            (2, 0),
204            (3, 1),
205            (3, 2),
206            (3, 4),
207            (4, 3),
208            (4, 5),
209            (5, 6),
210            (6, 4),
211            (7, 6),
212            (7, 7),
213        ];
214        for (u, v) in edges {
215            scc_add_edge(&mut g, u, v);
216        }
217        let count = scc_count(&g);
218        assert!(count >= 3);
219    }
220}