🦀 Functional Rust

800. Floyd-Warshall: All-Pairs Shortest Paths

Difficulty: 4 Level: Advanced Compute shortest paths between every pair of nodes in O(V³) — with path reconstruction via a `next` matrix and negative-cycle detection.

The Problem This Solves

When you need distances between all pairs of nodes — not just from one source — Floyd-Warshall is the right tool. Network latency matrices, road distance tables, transitive closure of reachability, and "six degrees of separation" style social network analysis all need all-pairs distances. Floyd-Warshall is simpler to implement than running Dijkstra V times (especially when negative edges are present), and for dense graphs where E ≈ V², it's asymptotically comparable. The `next` matrix variant makes path reconstruction O(path length) after O(V³) preprocessing — extremely useful when you're building a routing table that will be queried many times.

The Intuition

Ask: "does routing through node k improve the path from i to j?" Do this for every k from 0 to V-1. After considering all intermediate nodes, `dist[i][j]` holds the shortest path. This is the "intermediate node induction" insight: after the outer loop over k, `dist[i][j]` is the shortest path that only uses nodes 0..=k as intermediates. The diagonal check `dist[i][i] < 0` after the main loop reveals negative cycles. OCaml implements this with three nested loops on a 2D array; Rust uses exactly the same structure with `Vec<Vec<i64>>`.

How It Works in Rust

const INF: i64 = i64::MAX / 2;

// O(V³) time, O(V²) space
fn floyd_warshall(
 n: usize,
 edges: &[(usize, usize, i64)],
) -> (Vec<Vec<i64>>, Vec<Vec<Option<usize>>>, bool) {
 let mut dist = vec![vec![INF; n]; n];
 let mut next: Vec<Vec<Option<usize>>> = vec![vec![None; n]; n];

 for i in 0..n { dist[i][i] = 0; }
 for &(u, v, w) in edges {
     if w < dist[u][v] {
         dist[u][v] = w;
         next[u][v] = Some(v);
     }
 }

 // Main loop: try routing through each intermediate node k
 for k in 0..n {
     for i in 0..n {
         for j in 0..n {
             if dist[i][k] < INF && dist[k][j] < INF {
                 let via = dist[i][k] + dist[k][j];
                 if via < dist[i][j] {
                     dist[i][j] = via;
                     next[i][j] = next[i][k];  // route through k
                 }
             }
         }
     }
 }

 // Negative cycle: any node with negative self-distance
 let neg_cycle = (0..n).any(|i| dist[i][i] < 0);
 (dist, next, neg_cycle)
}

// Path reconstruction: follow next[src][dst] until we arrive
fn reconstruct(next: &Vec<Vec<Option<usize>>>, src: usize, dst: usize) -> Option<Vec<usize>> {
 if next[src][dst].is_none() { return None; }
 let mut path = vec![src];
 let mut v = src;
 while v != dst { v = next[v][dst]?; path.push(v); }
 Some(path)
}
The `next[i][j] = next[i][k]` update in the reconstruction matrix is subtle: when you route i→j through k, the first hop from i is still `next[i][k]` — because the full path from k to j is already encoded in the `next` matrix.

What This Unlocks

Key Differences

ConceptOCamlRust
2D matrix`Array.make_matrix n n inf``vec![vec![INF; n]; n]`
`next` matrix`int option array array``Vec<Vec<Option<usize>>>`
Loop order`for k`, `for i`, `for j`Identical — order is critical
Negative cycleCheck diagonal after loop`.any(idist[i][i] < 0)`
Path reconstructionRecursive via `next`Iterative `while v != dst` with `?` propagation
// Floyd-Warshall — all-pairs shortest paths O(V³)
const INF: i64 = i64::MAX / 2;

fn floyd_warshall(n: usize, edges: &[(usize, usize, i64)]) -> (Vec<Vec<i64>>, Vec<Vec<Option<usize>>>, bool) {
    let mut dist = vec![vec![INF; n]; n];
    let mut next: Vec<Vec<Option<usize>>> = vec![vec![None; n]; n];

    for i in 0..n { dist[i][i] = 0; }
    for &(u, v, w) in edges {
        if w < dist[u][v] {
            dist[u][v] = w;
            next[u][v] = Some(v);
        }
    }

    for k in 0..n {
        for i in 0..n {
            for j in 0..n {
                if dist[i][k] < INF && dist[k][j] < INF {
                    let via = dist[i][k] + dist[k][j];
                    if via < dist[i][j] {
                        dist[i][j] = via;
                        next[i][j] = next[i][k];
                    }
                }
            }
        }
    }

    let neg_cycle = (0..n).any(|i| dist[i][i] < 0);
    (dist, next, neg_cycle)
}

fn reconstruct(next: &Vec<Vec<Option<usize>>>, src: usize, dst: usize) -> Option<Vec<usize>> {
    if next[src][dst].is_none() { return None; }
    let mut path = vec![src];
    let mut v = src;
    while v != dst {
        v = next[v][dst]?;
        path.push(v);
    }
    Some(path)
}

fn main() {
    let edges = vec![(0,1,3i64),(0,3,7),(1,0,8),(1,2,2),(2,0,5),(2,3,1),(3,0,2)];
    let n = 4;
    let (dist, next, nc) = floyd_warshall(n, &edges);
    println!("Negative cycle: {nc}");
    for i in 0..n {
        for j in 0..n {
            if dist[i][j] >= INF {
                println!("  {i}->{j}: INF");
            } else {
                println!("  {i}->{j}: {}  {:?}", dist[i][j], reconstruct(&next, i, j));
            }
        }
    }
}
(* Floyd-Warshall — all-pairs shortest paths O(V³) *)
let infinity = max_int / 2

let floyd_warshall n edges =
  let dist = Array.init n (fun i -> Array.init n (fun j ->
    if i = j then 0 else infinity
  )) in
  let next = Array.init n (fun _ -> Array.make n (-1)) in
  List.iter (fun (u, v, w) ->
    if w < dist.(u).(v) then begin
      dist.(u).(v) <- w;
      next.(u).(v) <- v
    end
  ) edges;
  for k = 0 to n - 1 do
    for i = 0 to n - 1 do
      for j = 0 to n - 1 do
        if dist.(i).(k) < infinity && dist.(k).(j) < infinity then begin
          let via = dist.(i).(k) + dist.(k).(j) in
          if via < dist.(i).(j) then begin
            dist.(i).(j) <- via;
            next.(i).(j) <- next.(i).(k)
          end
        end
      done
    done
  done;
  let neg_cycle = Array.exists (fun row -> row < 0) (Array.init n (fun i -> dist.(i).(i))) in
  (dist, next, neg_cycle)

let reconstruct next src dst =
  if next.(src).(dst) = -1 then None
  else begin
    let path = ref [src] in
    let v    = ref src in
    while !v <> dst do
      v := next.(!v).(dst);
      path := !v :: !path
    done;
    Some (List.rev !path)
  end

let () =
  let edges = [(0,1,3);(0,3,7);(1,0,8);(1,2,2);(2,0,5);(2,3,1);(3,0,2)] in
  let n = 4 in
  let (dist, next, nc) = floyd_warshall n edges in
  Printf.printf "Negative cycle: %b\n" nc;
  for i = 0 to n-1 do
    for j = 0 to n-1 do
      if dist.(i).(j) >= infinity then
        Printf.printf "  %d->%d: INF\n" i j
      else begin
        let path_str = match reconstruct next i j with
          | None   -> "none"
          | Some p -> String.concat "->" (List.map string_of_int p)
        in
        Printf.printf "  %d->%d: %d  [%s]\n" i j dist.(i).(j) path_str
      end
    done
  done