The Story That Explains Floyd-Warshall
The postmaster's trick: he doesn't try to find all routes at once. Instead, he asks one brilliant question at a time: "If I'm allowed to pass through City 1 as a relay, does any route become shorter?" He updates his table. Then he asks the same question for City 2, City 3, and so on. After asking about every city, he has the complete shortest-path table — for ALL pairs at once.
That incremental relay trick is the entire soul of Floyd-Warshall.
The Floyd-Warshall algorithm, described in CLRS Chapter 25, solves the All-Pairs Shortest Paths (APSP) problem. Unlike Dijkstra (one source → all destinations) or Bellman-Ford (one source, handles negatives), Floyd-Warshall finds shortest paths between every possible pair of vertices in a single run — including graphs with negative edge weights, as long as there are no negative-weight cycles.
Floyd-Warshall uses dynamic programming. The key subproblem is: What is the shortest path from i to j using only vertices {1, 2, …, k} as intermediate stops? We solve this for k = 0, 1, 2, …, n, building up the complete answer one relay city at a time.
Formal Definition & The Recurrence
Let G = (V, E) be a weighted directed graph with n vertices
numbered 1 through n. Let w(i, j) be the weight of edge (i→j).
At step k, for every pair (i, j), we ask exactly one question: "Is going i → k → j cheaper than whatever shortest route I already know?" If yes, update. If no, keep the old answer. After asking this for every k from 1 to n, all shortest paths have been found. That's it. That's the whole algorithm.
Animated Worked Example — Step by Step
Let's trace Floyd-Warshall on a concrete 4-vertex graph. Watch the distance matrix evolve as each intermediate vertex k is processed. Click Next Step to advance one iteration at a time.
Highlighted = active relay vertex
| i\j | 1 | 2 | 3 | 4 |
|---|
After processing all 4 intermediate vertices, the matrix holds the shortest distance between every pair of vertices — including paths that go through 3 intermediate hops. The key discovery: path 1→3 via 3→4→1 chain doesn't need explicit enumeration; it emerges automatically from the DP recurrence.
The CLRS Pseudocode — Line by Line
Below is the CLRS pseudocode for Floyd-Warshall annotated with exactly what each line does. Click the code lines in the interactive tracer below to see the explanation for that specific line.
FLOYD-WARSHALL(W) n = W.rows D⁰ = W for k = 1 to n for i = 1 to n for j = 1 to n dk[i][j] = min(dk-1[i][j], dk-1[i][k] + dk-1[k][j]) return Dn
Python Implementation with Code Animation
Below is a clean, production-ready Python implementation matching the CLRS description. Use the Step Through button to advance one major operation at a time and see exactly what's happening inside the algorithm.
import math def floyd_warshall(W): # W: n×n weight matrix (use math.inf for no edge) n = len(W) D = [row[:] for row in W] # deep copy pi = [[None]*n for _ in range(n)] # predecessor # Initialise predecessor matrix for i in range(n): for j in range(n): if i != j and W[i][j] != math.inf: pi[i][j] = i # direct edge exists for k in range(n): # relay vertex for i in range(n): # source for j in range(n): # destination through_k = D[i][k] + D[k][j] if through_k < D[i][j]: D[i][j] = through_k pi[i][j] = pi[k][j] # update predecessor # Detect negative cycles (optional safety check) for i in range(n): if D[i][i] < 0: raise ValueError("Negative-weight cycle detected") return D, pi
Path Reconstruction with the Predecessor Matrix
The distance matrix D tells you how far. The predecessor matrix π tells you how to get there. Here's the CLRS path reconstruction function and a concrete example.
def print_path(pi, i, j):
# Print the shortest path from vertex i to vertex j
# pi: predecessor matrix returned by floyd_warshall()
# Vertices are 0-indexed internally
if i == j:
print(i + 1) # display 1-indexed
elif pi[i][j] is None:
print(f"No path from {i+1} to {j+1}")
else:
print_path(pi, i, pi[i][j]) # recurse through predecessors
print(j + 1)
# Using the 4-vertex example from Section 03
import math
INF = math.inf
W = [
[0, 3, 8, INF],
[INF, 0, INF, 1 ],
[INF, 4, 0, -5 ],
[2, INF, INF, 0 ]
]
D, pi = floyd_warshall(W)
print(f"Shortest 1→4: distance = {D[0][3]}")
print_path(pi, 0, 3) # 0-indexed: vertex 1→4
π[i][j] stores the vertex that immediately precedes j on the shortest i→j path. To reconstruct: look at π[i][j], then π[i][π[i][j]], and so on recursively until you reach i. For vertex 1→4 above: π[0][3]=2 (vertex 3), π[0][2]=0 (vertex 1). So path is 1 → 3 → 4, giving distance 8 + (−5) = 3 → wait, actually 1→3→4: 8 + (−5) = 3? Let's trace again: initial D[0][2]=8 (1→3), D[2][3]=−5 (3→4), so via k=3: D[0][3] = 8+(−5) = 3. Then via k=4: D[3][0]=2, D[0][3] could become D[0][3] via 4? 3 < 3, no change. Final answer: -1 via 1→2→4 (3+1=4) or 1→3→4 (8-5=3)... try k=2: D[0][1]+D[1][3]=3+1=4. So shortest is via path that accumulates best: final -1 comes from longer chains resolved in later k steps.
Complexity Analysis
| Algorithm | Problem Solved | Time | Negative Edges? | Negative Cycles? |
|---|---|---|---|---|
| Floyd-Warshall | All-pairs shortest path | Θ(n³) | ✓ Yes | Detects them |
| Dijkstra (once per source) | All-pairs (dense graphs) | O(n³) | ✗ No | ✗ No |
| Bellman-Ford (once per source) | All-pairs (any weights) | O(n²·E) | ✓ Yes | ✓ Detects |
| Johnson's Algorithm | All-pairs shortest path | O(n² log n + nE) | ✓ Yes | ✓ Detects |
For dense graphs (E ≈ n²), Floyd-Warshall's Θ(n³) matches Johnson's complexity and is far simpler to implement. For sparse graphs (E ≪ n²), Johnson's algorithm wins significantly. CLRS recommends Floyd-Warshall for dense graphs and Johnson's for sparse ones.
Negative Cycles — Detection & Why They Matter
| Edge | Weight |
|---|---|
| A → B | 5 |
| B → C | −8 |
| C → A | 2 |
| Cycle sum | −1 ❌ |
| Check | Value |
|---|---|
| D[A][A] | −1 (negative!) |
| D[B][B] | −1 (negative!) |
| D[C][C] | −1 (negative!) |
| Conclusion | Negative cycle detected — raise error |
CLRS Theorem 25.1 states: after running Floyd-Warshall, the graph contains
a negative-weight cycle if and only if there exists a vertex i such that
D[i][i] < 0. Simply checking the diagonal is sufficient.
Visual Diagram — The DP "Relay" Concept
The diagram below illustrates the key insight: at step k, we test whether routing through vertex k as a relay improves any existing path. The purple arc shows the "via k" route.
Real-World Use Cases & Benefits
Bonus: Transitive Closure (CLRS §25.2)
CLRS Section 25.2 presents a variant that computes the transitive closure of a directed graph — answering "can vertex j be reached from vertex i?" for all pairs. The structure is identical to Floyd-Warshall but uses Boolean algebra instead of arithmetic.
def transitive_closure(adj):
# adj: n×n adjacency matrix (True/False or 1/0)
# Returns: n×n reachability matrix T
n = len(adj)
# T[i][j] = True if j is reachable from i
T = [[adj[i][j] or (i == j) for j in range(n)] for i in range(n)]
for k in range(n): # relay vertex k
for i in range(n):
for j in range(n):
# Reachable directly OR via k
T[i][j] = T[i][j] or (T[i][k] and T[k][j])
return T
# Example: 4-vertex DAG
adj = [
[False, True, False, False], # 1→2
[False, False, True, False], # 2→3
[False, False, False, True ], # 3→4
[False, False, False, False], # no outgoing
]
T = transitive_closure(adj)
print(f"Can 1 reach 4? {T[0][3]}") # True: 1→2→3→4
print(f"Can 4 reach 1? {T[3][0]}") # False: no back edges
In standard Floyd-Warshall, the "semiring" is (min, +). For transitive closure, we switch to the Boolean semiring (or, and). Both operate over the same three-nested-loop structure — the algorithm is identical, only the operators change. This generalisation is discussed in CLRS as "algebraic paths."
CLRS Chapter 25 Context — Where Floyd-Warshall Fits
Golden Rules — Floyd-Warshall in Practice
INF + INF
becomes a large negative number in some languages, producing wrong updates.
T[i] |= T[k] using Python integers
as bitmasks (each row is an integer, one bit per vertex). Reduces constant factor
by a factor of 64 on 64-bit systems.
Algorithm Comparison — APSP Methods
| Property | Floyd-Warshall | Johnson's Algorithm | n × Bellman-Ford |
|---|---|---|---|
| Time complexity | Θ(n³) | O(n² log n + nE) | O(n²·E) |
| Negative edges | ✓ Handles | ✓ Handles (reweighting) | ✓ Handles |
| Negative cycle detection | ✓ D[i][i] < 0 | ✓ via Bellman-Ford phase | ✓ per-source detection |
| Best for graph type | Dense (E ≈ n²) | Sparse (E ≪ n²) | Rarely preferred |
| Implementation complexity | Very simple — 3 loops | Moderate — two phases | Moderate — nested |
| Space | Θ(n²) | Θ(n²) + O(E) | O(n + E) |
| Transitive closure variant | Trivial (or/and) | Not natural | Not natural |
Use Floyd-Warshall when: (a) you need all-pairs distances, (b) the graph is dense or small (n < 500), (c) there may be negative edges. The three-line inner loop is so simple to implement and so cache-friendly that it often outperforms theoretically better algorithms on real hardware for graphs of practical size.