805-kosaraju-scc — Kosaraju's Strongly Connected Components
Tutorial Video
Text description (accessibility)
This video demonstrates the "805-kosaraju-scc — Kosaraju's Strongly Connected Components" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Kosaraju's algorithm (1978, independently by Sharir 1981) finds SCCs using two DFS passes: first on the original graph to compute finish-time order, then on the reversed graph in reverse finish order. Key difference from OCaml: 1. **Code simplicity**: Kosaraju's two
Tutorial
The Problem
Kosaraju's algorithm (1978, independently by Sharir 1981) finds SCCs using two DFS passes: first on the original graph to compute finish-time order, then on the reversed graph in reverse finish order. Each DFS tree in the second pass is exactly one SCC. While Tarjan's uses one pass, Kosaraju's is conceptually simpler and easier to implement correctly. Both run in O(V+E) time.
🎯 Learning Outcomes
radj by swapping edge directionsfinish_order: Vec<usize> stack populated during the first DFSdfs2 call on an unvisited vertex in reverse finish order yields exactly one SCCCode Example
#![allow(clippy::all)]
//! # Kosaraju's SCC Algorithm
pub fn kosaraju(n: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![vec![]; n];
let mut radj = vec![vec![]; n];
for &(u, v) in edges {
adj[u].push(v);
radj[v].push(u);
}
let mut visited = vec![false; n];
let mut order = vec![];
fn dfs1(v: usize, adj: &[Vec<usize>], vis: &mut [bool], ord: &mut Vec<usize>) {
vis[v] = true;
for &u in &adj[v] {
if !vis[u] {
dfs1(u, adj, vis, ord);
}
}
ord.push(v);
}
fn dfs2(v: usize, radj: &[Vec<usize>], vis: &mut [bool], comp: &mut Vec<usize>) {
vis[v] = true;
comp.push(v);
for &u in &radj[v] {
if !vis[u] {
dfs2(u, radj, vis, comp);
}
}
}
for v in 0..n {
if !visited[v] {
dfs1(v, &adj, &mut visited, &mut order);
}
}
visited.fill(false);
let mut sccs = vec![];
for &v in order.iter().rev() {
if !visited[v] {
let mut comp = vec![];
dfs2(v, &radj, &mut visited, &mut comp);
sccs.push(comp);
}
}
sccs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kosaraju() {
let sccs = kosaraju(5, &[(0, 1), (1, 2), (2, 0), (1, 3), (3, 4)]);
assert!(!sccs.is_empty());
}
}Key Differences
OCaml Approach
OCaml's two-function pattern mirrors the two-pass algorithm naturally. let rec dfs1 v = ... order := v :: !order and let rec dfs2 v = comp := v :: !comp. The reversed graph uses Hashtbl for edge storage. OCaml's List.iter (fun v -> if not vis.(v) then ...) (List.rev !order) drives the second pass. The Ocamlgraph library offers Kosaraju's as an alternative to Tarjan's.
Full Source
#![allow(clippy::all)]
//! # Kosaraju's SCC Algorithm
pub fn kosaraju(n: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![vec![]; n];
let mut radj = vec![vec![]; n];
for &(u, v) in edges {
adj[u].push(v);
radj[v].push(u);
}
let mut visited = vec![false; n];
let mut order = vec![];
fn dfs1(v: usize, adj: &[Vec<usize>], vis: &mut [bool], ord: &mut Vec<usize>) {
vis[v] = true;
for &u in &adj[v] {
if !vis[u] {
dfs1(u, adj, vis, ord);
}
}
ord.push(v);
}
fn dfs2(v: usize, radj: &[Vec<usize>], vis: &mut [bool], comp: &mut Vec<usize>) {
vis[v] = true;
comp.push(v);
for &u in &radj[v] {
if !vis[u] {
dfs2(u, radj, vis, comp);
}
}
}
for v in 0..n {
if !visited[v] {
dfs1(v, &adj, &mut visited, &mut order);
}
}
visited.fill(false);
let mut sccs = vec![];
for &v in order.iter().rev() {
if !visited[v] {
let mut comp = vec![];
dfs2(v, &radj, &mut visited, &mut comp);
sccs.push(comp);
}
}
sccs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kosaraju() {
let sccs = kosaraju(5, &[(0, 1), (1, 2), (2, 0), (1, 3), (3, 4)]);
assert!(!sccs.is_empty());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kosaraju() {
let sccs = kosaraju(5, &[(0, 1), (1, 2), (2, 0), (1, 3), (3, 4)]);
assert!(!sccs.is_empty());
}
}
Deep Comparison
OCaml vs Rust: Kosaraju 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.