Floyd-Warshall
All-pairs shortest paths by dynamic programming over the set of allowed intermediate nodes: three nested loops, O(V³), handles negative edges.
Overview
Floyd-Warshall computes the shortest distance between every pair of nodes using an Adjacency Matrix and a single idea: d[i][j] after considering intermediate nodes {0..k} equals min(d[i][j], d[i][k] + d[k][j]) computed with intermediates {0..k-1}. Three nested loops, k outermost, and the matrix updates in place.
Preconditions and complexity: any weights including negative (no negative cycles for meaningful distances, but they are detectable via d[i][i] < 0), directed or undirected. Time O(V³), space O(V²). It is the right choice for dense graphs or when V ≤ ~400–500 and you need all pairs; on sparse graphs, running Dijkstra's Algorithm from every node costs O(V (V + E) log V), which is far less when E ≪ V².
Intuition
A mental model before the formal terms.
You have a table of direct flight prices between every pair of cities. Now open hub 0: for every pair, ask "is going via hub 0 cheaper than what I have?" and update the table. Then open hub 1, then hub 2, … After the last hub is opened, every entry is the cheapest itinerary using any combination of hubs. The order in which you open hubs is exactly the outer k loop, and it must be outermost — you must finish considering one hub for all pairs before opening the next.
How it works
- Initialise
d[i][i] = 0,d[i][j] = w(i, j)for edges (keep the minimum for parallel edges),∞otherwise. Setnext[i][j] = jfor edges to support path reconstruction. - For
kin0..V-1(outermost): for everyi, for everyj: ifd[i][k] + d[k][j] < d[i][j], setd[i][j]to that sum andnext[i][j] = next[i][k]. - After the loops,
d[i][j]is the shortest distance. If anyd[i][i] < 0, a negative cycle passes throughi; every pair that can reach and leave such a node has distance-∞. - Path from
itoj: start ati, repeatedly move tonext[cur][j]untilcur == j. - Loop order matters: with
kinside, the updated[i][j]viakwould use ad[i][k]that has not yet been allowed to use intermediates< kfor all rows, breaking the DP.iandjare interchangeable; onlykmust be outermost.
Why it works
Define D_k[i][j] = shortest path from i to j whose intermediate nodes all lie in {0, …, k-1}. Either the optimal such path avoids node k-1 (D_{k-1}[i][j]) or it passes through it exactly once (D_{k-1}[i][k-1] + D_{k-1}[k-1][j]). That is the recurrence; D_0 is the edge matrix and D_V is the answer.
In-place update is safe because in round k, d[i][k] and d[k][j] do not change: d[i][k] = min(d[i][k], d[i][k] + d[k][k]) and d[k][k] = 0 (absent negative cycles), so row k and column k are fixed points during round k.
Negative cycle detection: a cycle through i of negative total weight makes d[i][i] negative once every node on the cycle has been used as k.
Recognition
How to tell a problem wants this.
- "Distance between every pair", "for each query (u, v)", many queries on a small graph.
V ≤ 500in the constraints — a strong hint thatO(V³)is intended.- Transitive closure (reachability for all pairs) — same loops with boolean OR/AND.
- Minimax/maximin path between all pairs (replace
+withmaxandminwithmin/max).
Interactive visualization
Play, step, change the input. ← → and space work too.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 3 | ∞ | ∞ | 8 |
| B | ∞ | 0 | 1 | 7 | ∞ |
| C | 2 | ∞ | 0 | 1 | ∞ |
| D | ∞ | ∞ | ∞ | 0 | 2 |
| E | 4 | ∞ | ∞ | ∞ | 0 |
1dist[i][j] = w(i,j) if edge, 0 if i == j, else ∞2for k in nodes: # allowed intermediate3 for i in nodes:4 for j in nodes:5 if dist[i][k] + dist[k][j] < dist[i][j]:6 dist[i][j] = dist[i][k] + dist[k][j]7return distPseudocode
1d = adjacency matrix with INF for missing edges, d[i][i] = 02for k in 0..n-1:3 for i in 0..n-1:4 for j in 0..n-1:5 if d[i][k] + d[k][j] < d[i][j]:6 d[i][j] = d[i][k] + d[k][j]; next[i][j] = next[i][k]7negative cycle iff any d[i][i] < 0Implementations
1from math import inf2 3 4def floyd_warshall(5 n: int, edges: list[tuple[int, int, int]]6) -> tuple[list[list[float]], list[list[int]]]:7 """edges = directed (u, v, w) triples; nodes 0..n-1.8 Returns (dist, nxt): all-pairs distances (inf = unreachable) and9 nxt[i][j] = first hop on the shortest i -> j path (-1 = unreachable)."""101 · Initialize distance and next-hop matrices11 dist: list[list[float]] = [[inf] * n for _ in range(n)]12 nxt = [[-1] * n for _ in range(n)]13 for i in range(n):14 dist[i][i] = 015 nxt[i][i] = i162 · Seed direct edges (keep the minimum over parallel edges)17 for u, v, w in edges:18 if w < dist[u][v]:19 dist[u][v] = w20 nxt[u][v] = v213 · Allow each node k as an intermediate (k must be the outer loop)22 for k in range(n):23 row_k = dist[k] # hoist row lookups out of the inner loop24 for i in range(n):25 d_ik = dist[i][k]26 if d_ik == inf: # row skip: nothing goes through k from i27 continue28 row_i = dist[i]29 for j in range(n):30 cand = d_ik + row_k[j]31 if cand < row_i[j]:32 row_i[j] = cand33 nxt[i][j] = nxt[i][k]34 return dist, nxt35 36 374 · Reconstruct a path by following next hops38def fw_path(nxt: list[list[int]], u: int, v: int) -> list[int]:39 if nxt[u][v] == -1:40 return []41 path = [u]42 while u != v:43 u = nxt[u][v]44 path.append(u)45 return path46 47 485 · Negative cycle iff some diagonal entry went negative49def has_negative_cycle(dist: list[list[float]]) -> bool:50 return any(dist[i][i] < 0 for i in range(len(dist)))- Matrices are built with list comprehensions —
[[inf] * n for _ in range(n)]creates n independent rows. - Hoisting
row_k = dist[k]androw_i = dist[i]out of the inner loop avoids repeated list indexing, roughly halving the runtime in CPython. - The
d_ik == infskip removes entire inner loops for unreachable(i, k)pairs. fw_pathwalks thenxtmatrix;has_negative_cyclescans the diagonal with a generator expression.
Pure-Python triple loops manage ~10⁷ iterations per second: V = 200 is fine, V = 500 (1.25·10⁸) takes tens of seconds — reach for numpy (repeated np.minimum over broadcasts) beyond that.
[[inf] * n] * nwould alias one row n times — the comprehension is mandatory.- A numpy formulation does round k as
d = np.minimum(d, d[:, k:k+1] + d[k]), vectorising the two inner loops. infis a float; if exact integer distances matter downstream, replace reachable entries withint(...)at the end.
- Row aliasing via
* non a list of lists. - Not hoisting row references and wondering why Python is 3x slower than expected.
- Using a huge int like
10**18as INF and forgetting it silently participates in comparisons as a real value after one bad addition.
- Infinity: JS/TS
Infinityand Pythonmath.infabsorb addition, soINF + INFis harmless; C++ must shrink its integer sentinel (MAX/4) or guard every addition —long longoverflow is undefined behaviour. - 2D-array construction pitfalls differ: Python
[[inf] * n] * nand JSArray(n).fill(row)both alias a single row object; C++vectorconstruction copies properly by design. - Inner-loop speed: C++ runs V=500 in well under a second; JS/TS manage it in hundreds of ms; CPython needs row-hoisting and still struggles past V≈300 — numpy vectorisation is the practical fix.
- The
k-outermost loop order is an algorithmic invariant in all four languages — no compiler or type system can catch it if you get it wrong.
Complexity
No early exit. V = 500 → 1.25·10^8 inner iterations, fine; V = 5000 → 1.25·10^11, infeasible. Constant factor is tiny (no heap, cache-friendly row access).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- All-pairs distances on a small (
V ≤ ~500) or dense graph. - Negative edges are present and you need all pairs (Dijkstra-from-every-node is invalid).
- Many distance queries after a one-time precomputation.
- Transitive closure, minimax paths, or any semiring variant — same triple loop with different operators.
- Large sparse graphs —
O(V³)andO(V²)memory are both prohibitive; run Dijkstra's Algorithm (or Breadth-First Search (BFS)) from each node, or Johnson's algorithm with negative edges. - A single source — Dijkstra's Algorithm/Bellman-Ford are far cheaper.
- One query between one pair — even Bellman-Ford beats it.
Alternatives
Common mistakes
- Putting
kin the inner loop — produces wrong (too large) distances on many graphs. - Adding
INF + INFin fixed-width integers → overflow into negative "improvements". Guardd[i][k] != INFand useINF = MAX/4, or use floatingInfinity. - Not taking the minimum over parallel edges during initialisation.
- Reconstructing the path with a
prevmatrix but updating it as if it werenext(or vice versa) —next[i][j] = next[i][k]versusprev[i][j] = prev[k][j]. - Reporting finite distances for pairs affected by a negative cycle; check
d[i][i] < 0and propagate-∞if the task requires it.
Interview patterns
- "Find the city with the smallest number of neighbours within distance threshold" — Floyd-Warshall then count per row.
- Transitive closure:
reach[i][j] |= reach[i][k] && reach[k][j](course prerequisites queries). - Widest/bottleneck path for all pairs:
d[i][j] = max(d[i][j], min(d[i][k], d[k][j])). - Shortest cycle (girth) in a weighted digraph: minimum
d[i][i]after the loops when self-distances are initialised to∞.
- Network Delay TimeAdvanced
- Coin ChangeIntermediate