804-tarjan-scc — Tarjan's Strongly Connected Components
Tutorial Video
Text description (accessibility)
This video demonstrates the "804-tarjan-scc — Tarjan's Strongly Connected Components" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. A Strongly Connected Component (SCC) is a maximal subgraph where every vertex can reach every other vertex. Key difference from OCaml: 1. **Recursion vs iteration**: Recursive Tarjan's risks stack overflow on large graphs; Rust's implementation uses an explicit stack to avoid this; OCaml's `ulimit
Tutorial
The Problem
A Strongly Connected Component (SCC) is a maximal subgraph where every vertex can reach every other vertex. Tarjan's algorithm (1972) finds all SCCs in O(V+E) time using a single DFS, tracking discovery times and low-link values. SCCs are used in compiler optimization (detecting cycles in dataflow graphs), social network analysis (finding tight-knit communities), deadlock detection, and 2-SAT (satisfiability) solvers.
🎯 Learning Outcomes
disc) and low-link value (low) per vertex during DFSlow[v] == disc[v] after all descendants are processedCode Example
#![allow(clippy::all)]
//! # Tarjan's SCC Algorithm
pub fn tarjan_scc(n: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![vec![]; n];
for &(u, v) in edges {
adj[u].push(v);
}
let mut index = 0;
let mut indices = vec![None; n];
let mut low = vec![0; n];
let mut on_stack = vec![false; n];
let mut stack = vec![];
let mut sccs = vec![];
fn dfs(
v: usize,
adj: &[Vec<usize>],
index: &mut usize,
indices: &mut [Option<usize>],
low: &mut [usize],
on_stack: &mut [bool],
stack: &mut Vec<usize>,
sccs: &mut Vec<Vec<usize>>,
) {
indices[v] = Some(*index);
low[v] = *index;
*index += 1;
stack.push(v);
on_stack[v] = true;
for &w in &adj[v] {
if indices[w].is_none() {
dfs(w, adj, index, indices, low, on_stack, stack, sccs);
low[v] = low[v].min(low[w]);
} else if on_stack[w] {
low[v] = low[v].min(indices[w].unwrap());
}
}
if indices[v] == Some(low[v]) {
let mut scc = vec![];
while let Some(w) = stack.pop() {
on_stack[w] = false;
scc.push(w);
if w == v {
break;
}
}
sccs.push(scc);
}
}
for v in 0..n {
if indices[v].is_none() {
dfs(
v,
&adj,
&mut index,
&mut indices,
&mut low,
&mut on_stack,
&mut stack,
&mut sccs,
);
}
}
sccs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tarjan() {
let edges = [(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 5), (5, 3)];
let sccs = tarjan_scc(6, &edges);
assert_eq!(sccs.len(), 2);
}
}Key Differences
ulimit -s or Thread.create can work around the limit.disc, low, on_stack; both languages use mutable arrays directly.OCaml Approach
OCaml implements Tarjan's with ref cells for index_counter, on_stack: bool array, and stack: int list ref. Recursive DFS using let rec dfs v = ... is natural in OCaml. The Ocamlgraph library provides SCC.scc_list using Tarjan's or Kosaraju's algorithm. OCaml's exception mechanism can implement the "pop until root" with cleaner control flow.
Full Source
#![allow(clippy::all)]
//! # Tarjan's SCC Algorithm
pub fn tarjan_scc(n: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![vec![]; n];
for &(u, v) in edges {
adj[u].push(v);
}
let mut index = 0;
let mut indices = vec![None; n];
let mut low = vec![0; n];
let mut on_stack = vec![false; n];
let mut stack = vec![];
let mut sccs = vec![];
fn dfs(
v: usize,
adj: &[Vec<usize>],
index: &mut usize,
indices: &mut [Option<usize>],
low: &mut [usize],
on_stack: &mut [bool],
stack: &mut Vec<usize>,
sccs: &mut Vec<Vec<usize>>,
) {
indices[v] = Some(*index);
low[v] = *index;
*index += 1;
stack.push(v);
on_stack[v] = true;
for &w in &adj[v] {
if indices[w].is_none() {
dfs(w, adj, index, indices, low, on_stack, stack, sccs);
low[v] = low[v].min(low[w]);
} else if on_stack[w] {
low[v] = low[v].min(indices[w].unwrap());
}
}
if indices[v] == Some(low[v]) {
let mut scc = vec![];
while let Some(w) = stack.pop() {
on_stack[w] = false;
scc.push(w);
if w == v {
break;
}
}
sccs.push(scc);
}
}
for v in 0..n {
if indices[v].is_none() {
dfs(
v,
&adj,
&mut index,
&mut indices,
&mut low,
&mut on_stack,
&mut stack,
&mut sccs,
);
}
}
sccs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tarjan() {
let edges = [(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 5), (5, 3)];
let sccs = tarjan_scc(6, &edges);
assert_eq!(sccs.len(), 2);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tarjan() {
let edges = [(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 5), (5, 3)];
let sccs = tarjan_scc(6, &edges);
assert_eq!(sccs.len(), 2);
}
}
Deep Comparison
OCaml vs Rust: Tarjan Scc
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.