πŸ¦€ Functional Rust

804. Tarjan's Strongly Connected Components

Difficulty: 5 Level: Master Find all strongly connected components in a directed graph in a single DFS pass β€” O(V + E) β€” using discovery times and low-link values.

The Problem This Solves

A strongly connected component (SCC) is a maximal set of nodes where every node is reachable from every other. SCCs reveal the "true structure" of directed graphs. Compilers use SCC decomposition to detect circular dependencies between modules and to order compilation units. Social network analysis uses SCCs to find tightly-knit communities where influence flows bidirectionally. Web crawlers use SCCs to detect link farms and cyclic structures. Deadlock detection in concurrent systems models resource dependencies as a directed graph β€” a cycle indicates deadlock, and SCC is a generalisation.

The Intuition

During DFS, assign each node a discovery time (`disc`) when first visited, and a `low` value representing the smallest discovery time reachable from its subtree (including back edges). When we finish processing a node and `low[u] == disc[u]`, that node is the "root" of an SCC β€” pop everything off the auxiliary stack down to (and including) u; those nodes form one SCC. The key insight: if `low[u] == disc[u]`, no back edge from u's subtree reaches above u in the DFS tree, so u and everything below it forms an isolated component. Recursive DFS is natural in OCaml but risks stack overflow in Rust on large graphs. This implementation uses an explicit call stack β€” `Vec<(node, adj_index)>` β€” simulating the call stack manually.

How It Works in Rust

// O(V + E) β€” single DFS pass with explicit stack (no recursion overflow)
fn tarjan_scc(adj: &[Vec<usize>]) -> Vec<Vec<usize>> {
 let mut disc     = vec![usize::MAX; n]; // MAX = unvisited
 let mut low      = vec![0usize; n];
 let mut on_stack = vec![false; n];
 let mut stack    = Vec::new();          // Tarjan's auxiliary stack
 let mut timer    = 0usize;
 let mut sccs     = Vec::new();

 for start in 0..n {
     if disc[start] != usize::MAX { continue; }
     // Iterative DFS: each frame = (node, index into adj[node])
     let mut call_stack: Vec<(usize, usize)> = vec![(start, 0)];
     disc[start] = timer; low[start] = timer; timer += 1;
     stack.push(start); on_stack[start] = true;

     while let Some((u, idx)) = call_stack.last_mut() {
         let u = *u;
         if *idx < adj[u].len() {
             let v = adj[u][*idx]; *idx += 1;
             if disc[v] == usize::MAX {
                 // Tree edge: recurse into v
                 disc[v] = timer; low[v] = timer; timer += 1;
                 stack.push(v); on_stack[v] = true;
                 call_stack.push((v, 0));
             } else if on_stack[v] {
                 // Back edge: update low[u] with disc[v]
                 low[u] = low[u].min(disc[v]);
             }
         } else {
             // Done with u: propagate low to parent, check SCC root
             call_stack.pop();
             if let Some(&(parent, _)) = call_stack.last() {
                 low[parent] = low[parent].min(low[u]);
             }
             if low[u] == disc[u] {
                 // u is SCC root: pop the stack
                 let mut scc = Vec::new();
                 loop {
                     let w = stack.pop().unwrap();
                     on_stack[w] = false;
                     scc.push(w);
                     if w == u { break; }
                 }
                 sccs.push(scc);
             }
         }
     }
 }
 sccs
}
The `on_stack` array distinguishes back edges (to ancestors on the stack) from cross edges (to already-processed nodes). Only back edges update `low`.

What This Unlocks

Key Differences

ConceptOCamlRust
DFS implementationRecursive (natural, but stack-limited)Explicit `Vec<(node, adj_idx)>` call stack
Discovery timeMutable `ref` counter`usize` `timer` variable, incremented per visit
Auxiliary stack`Stack.t` or list`Vec<usize>` with `push`/`pop`
Low propagationDuring recursive returnAfter `call_stack.pop()`, propagate to `call_stack.last()`
SCC root detection`low.(u) = disc.(u)``if low[u] == disc[u]` β€” identical logic
// Tarjan's SCC β€” O(V+E), iterative DFS to avoid stack overflow

fn tarjan_scc(adj: &[Vec<usize>]) -> Vec<Vec<usize>> {
    let n = adj.len();
    let mut disc     = vec![usize::MAX; n];
    let mut low      = vec![0usize; n];
    let mut on_stack = vec![false; n];
    let mut stack    = Vec::new();
    let mut timer    = 0usize;
    let mut sccs     = Vec::new();

    for start in 0..n {
        if disc[start] != usize::MAX { continue; }
        // Iterative DFS: (node, index into adj list)
        let mut call_stack: Vec<(usize, usize)> = vec![(start, 0)];
        disc[start] = timer; low[start] = timer; timer += 1;
        stack.push(start); on_stack[start] = true;

        while let Some((u, idx)) = call_stack.last_mut() {
            let u = *u;
            if *idx < adj[u].len() {
                let v = adj[u][*idx];
                *idx += 1;
                if disc[v] == usize::MAX {
                    disc[v] = timer; low[v] = timer; timer += 1;
                    stack.push(v); on_stack[v] = true;
                    call_stack.push((v, 0));
                } else if on_stack[v] {
                    low[u] = low[u].min(disc[v]);
                }
            } else {
                call_stack.pop();
                if let Some(&(parent, _)) = call_stack.last() {
                    low[parent] = low[parent].min(low[u]);
                }
                if low[u] == disc[u] {
                    let mut scc = Vec::new();
                    loop {
                        let w = stack.pop().unwrap();
                        on_stack[w] = false;
                        scc.push(w);
                        if w == u { break; }
                    }
                    sccs.push(scc);
                }
            }
        }
    }
    sccs
}

fn main() {
    let n = 8;
    let mut adj = vec![vec![]; n];
    let mut add = |u: usize, v: usize| adj[u].push(v);
    add(0,1); add(1,2); add(2,0); add(2,3);
    add(3,4); add(4,5); add(5,3);
    add(6,5); add(6,7); add(7,6);

    let sccs = tarjan_scc(&adj);
    println!("Number of SCCs: {}", sccs.len());
    for (i, scc) in sccs.iter().enumerate() {
        println!("  SCC {}: {:?}", i + 1, scc);
    }
}
(* Tarjan's SCC β€” O(V+E) recursive DFS *)

let tarjan_scc adj n =
  let disc     = Array.make n (-1) in
  let low      = Array.make n 0 in
  let on_stack = Array.make n false in
  let stack    = ref [] in
  let timer    = ref 0 in
  let sccs     = ref [] in

  let rec dfs u =
    disc.(u) <- !timer;
    low.(u)  <- !timer;
    incr timer;
    stack    := u :: !stack;
    on_stack.(u) <- true;
    List.iter (fun v ->
      if disc.(v) = -1 then begin
        dfs v;
        low.(u) <- min low.(u) low.(v)
      end else if on_stack.(v) then
        low.(u) <- min low.(u) disc.(v)
    ) adj.(u);
    (* u is root of SCC *)
    if low.(u) = disc.(u) then begin
      let scc = ref [] in
      let cont = ref true in
      while !cont do
        match !stack with
        | [] -> cont := false
        | w :: rest ->
          stack := rest;
          on_stack.(w) <- false;
          scc := w :: !scc;
          if w = u then cont := false
      done;
      sccs := !scc :: !sccs
    end
  in
  for v = 0 to n - 1 do
    if disc.(v) = -1 then dfs v
  done;
  !sccs

let () =
  let n   = 8 in
  let adj = Array.make n [] in
  let add u v = adj.(u) <- v :: adj.(u) in
  add 0 1; add 1 2; add 2 0; add 2 3;
  add 3 4; add 4 5; add 5 3;
  add 6 5; add 6 7; add 7 6;
  let sccs = tarjan_scc adj n in
  Printf.printf "Number of SCCs: %d\n" (List.length sccs);
  List.iteri (fun i scc ->
    Printf.printf "  SCC %d: [%s]\n" (i+1)
      (String.concat "," (List.map string_of_int scc))
  ) sccs