A Bit of History
The familiar triple-loop pattern emerged through closely related work on reachability and shortest paths. Bernard Roy described a version in 1959; Stephen Warshall published the Boolean transitive-closure form in 1962; and Robert Floyd published “Algorithm 97: Shortest Path” that same year. This is why names such as Floyd–Warshall, Roy–Warshall, and Roy–Floyd–Warshall appear in different traditions. The deeper unity is algebraic: the same matrix update can combine reachability, distances, or other path summaries by changing the operations.
The DP State: What Has Been Allowed So Far?
Number the vertices $1,2,\ldots,V$. Define $d^{(k)}[i][j]$ as the shortest $i$-to-$j$ distance whose intermediate vertices may come only from $\{1,\ldots,k\}$. The endpoints $i$ and $j$ are not restricted by that set.
Every ordered pair $(i,j)$
One matrix cell represents one source–destination question. Directed pairs are distinct.
Allowed intermediates
Stage $k$ adds one possible transfer point; it does not force the path to use that vertex.
Best distance so far
A cell can only stay equal or decrease as more intermediates become legal.
Stage zero: direct travel only
- $d^{(0)}[i][i]=0$.
- $d^{(0)}[i][j]$ is the lightest direct edge $i\to j$ when one exists.
- $d^{(0)}[i][j]=\infty$ when no direct edge exists.
Taking the minimum over parallel edges during initialization matters: the DP assumes the base matrix already contains the best route with no intermediate vertices.
The Recurrence: Skip k or Use k
Any best path allowed at stage $k$ falls into one of two cases. It either avoids $k$, so the old answer remains valid, or it uses $k$, in which case the path splits into an $i$-to-$k$ prefix and a $k$-to-$j$ suffix:
Why k Must Be the Outermost Loop
The algorithm compresses the conceptual 3D table $d^{(k)}$ into one 2D matrix. The outer $k$ loop is the timeline: after its iteration, every cell must mean “intermediates through $k$ are allowed.” The $i$ and $j$ loops may be swapped, but moving $k$ inside them destroys that shared stage invariant and can leave some pairs using partially updated knowledge while others do not.
for k in vertices: # expand the allowed intermediate set
for i in vertices: # choose a source
for j in vertices: # choose a destination
if dist[i][k] != INF and dist[k][j] != INF:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
Loop Order Is Part of the Algorithm
Three loops over the same indices are not automatically equivalent. Keep $k$ outermost unless you are using a separately proved blocked or cache-optimized formulation.
Worked Example
Let $A\to B=3$, $B\to C=1$, and $A\to C=10$. Initially, only direct edges are allowed. When B becomes legal as an intermediate, the A-to-C cell discovers a cheaper two-edge route.
Why the DP Recurrence Works
Invariant: after completing outer iteration $k$, the matrix contains the best distances whose intermediate vertices are drawn only from $\{1,\ldots,k\}$.
Base case. Before any outer iteration, a legal path has no intermediate vertices, so it is either the zero-length diagonal path or one direct edge. The initialization is therefore correct.
Inductive step. Consider a shortest $i$-to-$j$ path allowed at stage $k$. If it avoids $k$, its value is already $d^{(k-1)}[i][j]$. If it uses $k$, then—when no relevant negative cycle makes the optimum unbounded below—we can choose an optimal walk in which $k$ need not be repeated. Splitting at $k$ produces an optimal prefix $i\leadsto k$ and suffix $k\leadsto j$, each using only earlier intermediates. Their sum is the second recurrence candidate. Taking the smaller case proves the invariant.
Why one matrix is enough
During stage $k$, entries such as $d[i][k]$ and $d[k][j]$ cannot gain a useful improvement merely by inserting $k$ again when no negative cycle is involved: that would add a closed walk through $k$ of nonnegative cost. Reading an already updated stage-$k$ value is therefore harmless, which justifies the in-place 2D implementation.
Reconstructing Actual Paths
Distances answer “how much?” A next matrix answers “where do I go first?” Initialize next[u][v] = v for each direct edge. Whenever routing $i\to k\to j$ improves a cell, copy the first hop of the improved prefix:
next[i][j] = next[i][k]To reconstruct $i\leadsto j$, repeatedly replace $i$ with next[i][j] until reaching $j$. If the distance is $\infty$, no path exists. If the pair is affected by a negative cycle, no finite shortest path exists and reconstruction must be rejected.
Detecting and Propagating Negative Cycles
After the main DP, $d[k][k]<0$ means some negative cycle is reachable from $k$ and can return to $k$. Any pair $(i,j)$ for which $i\leadsto k$ and $k\leadsto j$ can loop around that cycle arbitrarily many times before continuing, driving the route cost toward $-\infty$.
A Boolean Flag Is Not the Whole Story
A graph may contain a negative cycle that is irrelevant to some pairs. Mark exactly the affected pairs: for every negative-diagonal vertex $k$, any $(i,j)$ with finite $d[i][k]$ and $d[k][j]$ has distance $-\infty$ and no well-defined shortest path.
Complexity Analysis
The main computation considers all triples $(i,k,j)$:
$$\text{Time: } O(V^3) \qquad \text{Space: } O(V^2)$$
Maintaining a next matrix does not change these bounds. Explicitly marking all negative-cycle-affected pairs can add another $O(V^3)$ pass, still within the same asymptotic time.
The runtime does not benefit from sparsity: a graph with very few edges still pays for all $V^3$ triples. On the other hand, the regular matrix access pattern is simple, predictable, and often effective for modest or dense graphs. Blocked implementations can improve cache behavior without changing the recurrence.
Implementation
Each implementation keeps a distance matrix, a next-hop matrix, and an affected-pair matrix or set. It guards infinity before addition, keeps the lightest parallel edge, reconstructs finite paths, and rejects path requests whose cost is unbounded below.
| State | Meaning | Update |
|---|---|---|
dist[i][j] | Best distance under the current intermediate set | Take the smaller of the old value and the route through $k$ |
next[i][j] | First hop on the represented route | Copy next[i][k] when the route through $k$ wins |
affected[i][j] | Whether a reachable negative cycle can lie on an $i$-to-$j$ walk | Mark after the main DP using negative diagonal entries |
INF | No known route | Never add it to another distance |
Real-World Applications
Floyd–Warshall is attractive when the vertex set is modest, many or all pairwise answers are needed, and a matrix is a natural representation.
Precomputed routing costs
Compute every latency, transfer cost, or hop-adjusted route among a modest set of hubs, then answer pair queries in $O(1)$ time.
Diameter and centrality inputs
An all-pairs matrix directly supports eccentricity, diameter, average distance, and closeness-style measurements when unreachable pairs are handled explicitly.
Boolean transitive closure
Replace minimum with OR and addition with AND. The same stage invariant then answers whether every ordered pair is connected by some path.
Small dense transition systems
For a compact set of states with many transitions, precomputing every best transition cost can simplify later planning and analysis.
The semiring perspective
Ordinary shortest paths use “min” to choose between routes and “plus” to concatenate route costs. Reachability uses OR and AND. This substitution reveals Floyd–Warshall as a general closure pattern rather than a trick tied only to numeric distances.
Choosing an All-Pairs Shortest-Path Algorithm
| Method | Edge restrictions | Typical time | Best fit |
|---|---|---|---|
| Floyd–Warshall | Negative edges allowed; report affected pairs if cycles are negative | $O(V^3)$ | Modest or dense graphs; simple matrix implementation |
| Repeated Dijkstra | Nonnegative weights | $O(V(E+V)\log V)$ with binary heaps | Large sparse graphs |
| Johnson | Negative edges allowed, but no negative cycles | $O(VE\log V)$ for sparse graphs with binary heaps | Sparse graphs needing all pairs |
| Repeated BFS | Unweighted edges | $O(V(V+E))$ | Unweighted sparse graphs |
There is no universal vertex threshold: language overhead, graph density, cache behavior, query volume, and memory limits all matter. Benchmark representative inputs instead of relying on a single folklore cutoff.
Pitfalls & Debugging
| Symptom | Likely cause | Fix |
|---|---|---|
| Some multi-hop routes remain too large | $k$ is not the outer loop | Restore the stage order for k, then for i, then for j |
| Huge or negative nonsense values appear | Infinity was added, or fixed-width addition overflowed | Guard both subpaths before addition and use a safe sentinel or bounded arithmetic |
| A diagonal starts positive | Initialization copied a self-edge over the empty path | Initialize $d[i][i]=0$ and take the minimum with any explicit self-edge |
| Parallel edges produce the wrong base case | The last input edge overwrote a cheaper one | Initialize with the minimum weight for each ordered pair |
| Paths loop forever | Negative-cycle-affected pairs were sent to reconstruction | Propagate affected pairs and reject them before following next hops |
| An undirected graph behaves as directed | Only one direction of each input edge was initialized | Add both $u\to v$ and $v\to u$ explicitly |
Unreachable and Unbounded Are Different
$+\infty$ means no route exists. $-\infty$ means routes exist but no finite minimum does, because a usable negative cycle can be repeated. Keeping these states distinct prevents subtle downstream errors.
Exercises
Build the Intuition by Stages
- Trace: Add an edge $C\to A=2$ to the worked example and write the matrix after each outer-loop vertex.
- Reconstruct: Hand-simulate the
nextupdates that produce the path A → B → C. - Break it: Find a graph on which moving $k$ to the innermost loop leaves an incorrect distance.
- Diagnose: Add a negative cycle reachable from A but disconnected from another vertex D. Identify exactly which ordered pairs should become $-\infty$.
- Generalize: Implement Boolean Warshall by replacing min/plus with OR/AND, then compare its matrix to repeated graph searches.
- Measure: Benchmark Floyd–Warshall against repeated Dijkstra as graph density grows while $V$ stays fixed.
A compact mental model
Floyd–Warshall grows a permission set. At stage $k$, every matrix cell asks whether its best route should keep avoiding $k$ or split through $k$. The outer loop controls what paths are legal; the inner loops update every ordered pair under that shared rule.