DSA DSA In Depth
0%
lesson floyd-warshalljohnson-algorithmapsp

All-Pairs Shortest Paths: Floyd-Warshall DP Transitive Closure & Johnson's Potential Reweighting

7 min read Module 5.2: Disjoint Sets, Minimum Spanning Trees & Shortest Paths Updated Oct 23, 2026
📄 Cheat Sheet
Floyd-Warshall All-Pairs Shortest Path Algorithm
William Fiset Floyd-Warshall All-Pairs Shortest Path Algorithm Watch on YouTube ↗

Explains the intermediate-vertex DP state transition and negative cycle detection on the matrix diagonal.

0:00 / --:--

1. Floyd-Warshall: Intermediate Vertex Set Induction

Define $d^{(k)}[i][j]$ as the minimum weight of any path from $i$ to $j$ whose intermediate vertices belong strictly to the prefix set $\{0, 1, \dots, k\}$. Considering whether vertex $k$ is used as an intermediate stop along the shortest path from $i$ to $j$ yields the recurrence:

$$d^{(k)}[i][j] = \min\Big(d^{(k-1)}[i][j],\; d^{(k-1)}[i][k] + d^{(k-1)}[k][j]\Big)$$
Formal Invariant — Why $k$ Must Be the Outermost Loop & In-Place Space Compression

Why can we overwrite a single 2D matrix $d[i][j]$ in-place without corrupting phase $k$'s dependencies? Notice that during phase $k$, $d[i][j]$ reads $d[i][k]$ and $d[k][j]$. In phase $k$ itself, $d[i][k]$ is updated to $\min(d[i][k], d[i][k] + d[k][k])$. As long as no negative cycle passes through $k$ ($d[k][k] \ge 0$), $d^{(k)}[i][k] == d^{(k-1)}[i][k]$ and $d^{(k)}[k][j] == d^{(k-1)}[k][j]$! Thus in-place updates are exact.

Negative Cycle Detection: A vertex $u$ lies on a negative cycle if and only if after completion (or during execution) its diagonal entry satisfies $d[u][u] < 0$.

2. Johnson's Potential Reweighting Theorem (1977)

How can we eliminate negative edge weights without changing which path between $u$ and $v$ is shortest? (Simply adding a constant $+C$ to every edge is a classic blunder: it penalizes a 5-edge path by $+5C$ and a 2-edge path by $+2C$, changing the optimal path!)

Instead, assign every vertex $v \in V$ a scalar potential $h(v) \in \mathbb{R}$ and define the reweighted edge cost:

$$w_h(u, v) = w(u, v) + h(u) - h(v)$$

Along any path $P = \langle v_0, v_1, \dots, v_k \rangle$ from $v_0 = u$ to $v_k = v$, the intermediate potentials telescope and cancel completely:

$$w_h(P) = \sum_{i=1}^k \big(w(v_{i-1}, v_i) + h(v_{i-1}) - h(v_i)\big) = w(P) + h(u) - h(v)$$

Because $h(u) - h(v)$ depends only on the endpoints $(u, v)$ and not on the path $P$, every path from $u$ to $v$ shifts by the exact same constant $h(u) - h(v)$! To guarantee $w_h(u, v) \ge 0$ for all $(u, v) \in E$, we need $h(v) - h(u) \le w(u, v)$—which is satisfied by setting $h(v) = \delta(s^*, v)$ from a single Bellman-Ford run starting at a virtual super-source $s^*$!

Johnson's Potential Telescoping: w_h(u, v) = w(u, v) + h(u) - h(v) ≥ 0 u h=0 x h=-4 v h=-6 w = -4 w_h = -4 + 0 - (-4) = 0 w = -1 w_h = -1 + (-4) - (-6) = +1
flowchart LR Super["Add virtual s* with w(s*, v) = 0"] --> BF["Run Bellman-Ford from s* to get h[v] = dist(s*, v)"] BF --> Neg{"Negative cycle?"} Neg -- "Yes" --> Abort["Report Negative Cycle"] Neg -- "No" --> Reweight["Set w_h(u,v) = w(u,v) + h[u] - h[v] >= 0"] Reweight --> Dijk["Run Dijkstra from each u in V; recover dist(u,v) = d_h(u,v) - h[u] + h[v]"]
Bellman-Ford Negative-Cycle Witness, Floyd-Warshall k-Pivot & Johnson Reweighting
O(VE) Bellman-Ford · O(V³) Floyd · O(VE log V) Johnson

Witness negative cycles on the V-th relaxation round of Bellman-Ford, inspect Floyd-Warshall’s in-place DP matrix D^(k)[i][j] = min(D[i][j], D[i][k]+D[k][j]), and reweight negative edges to non-negative via Johnson potentials w_hat(u,v) = w(u,v) + h(u) - h(v) ≥ 0.

Step 1 / 8
Custom Input:
Timeline

Invariant & State

Rounds 1..V-1 (1..3): Relaxed all edges. Current distances d = [0, 1, 2, -1]. If no negative cycle exists, all shortest paths are settled.
w=4 w=-2 w=1 w=-1 v0 d=0 v1 d=1 v2 d=2 v3 d=-1
Bellman-Ford Pass
Round V-1 = 3
Negative Cycle
Checking...
Diff Constraints
Feasible so far
[Step 1] Rounds 1..V-1 (1..3): Relaxed all edges. Current distances d = [0, 1, 2, -1]. If no negative cycle exists, all shortest paths are settled.
Hardware Reality — Auto-Vectorization & Cache Row-Major Access in Floyd-Warshall

In Floyd-Warshall's loop order for k ... for i ... for j, the inner loop over j streams sequentially across row d[i][0..V-1] and row d[k][0..V-1] while holding d[i][k] in a scalar register. Compilers (-O3 -mavx2) auto-vectorize this inner loop using VPMINSD / VPADDD SIMD instructions, processing 8 distances per clock cycle!

Complexity Matrix

APSP Algorithm Negative Edges? Time Complexity Space Complexity
$V \times$ Dijkstra No ($w \ge 0$ only) O(V E + V² log V) O(V²) output
Floyd-Warshall DP Yes (detects $d[i][i] < 0$) O(V³) O(V²)
Johnson's Algorithm Yes (1 BF + $V$ Dijkstras) O(V E + V² log V) O(V² + E)
Warshall Bitset Transitive Closure Unweighted Reachability O(V³ / 64) V² / 8 bytes

Production Implementation: Floyd-Warshall & Johnson's Potential Reweighting

#include <vector>
#include <algorithm>
#include <limits>

void floydWarshall(int n, std::vector<std::vector<long long>>& dist) {
    constexpr long long INF = std::numeric_limits<long long>::max() / 4;
    for (int k = 0; k < n; ++k) {
        for (int i = 0; i < n; ++i) {
            if (dist[i][k] >= INF) continue; // Crucial overflow guard + branch skip!
            for (int j = 0; j < n; ++j) {
                if (dist[k][j] < INF) {
                    dist[i][j] = std::min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
}
Failure Mode — Writing for i ... for j ... for k in Floyd-Warshall

Permuting the loops of Floyd-Warshall to place k on the inside (for i ... for j ... for k) is one of the most notorious bugs in competitive and production programming: it only considers paths of length $\le 2$ (single intermediate hop $i \to k \to j$) or corrupts transitive dependencies across multi-hop paths! The intermediate vertex k must always be the outermost loop.

Check yourself Question 1 of 4
Concept Check Why Intermediate Vertex k Must Be the Outer Loop recall
Section: Floyd-Warshall Loop Order

In Floyd-Warshall's triple nested loop, why must the intermediate vertex `k` be the outermost loop (`for k ... for i ... for j`) rather than the innermost loop?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

On a sparse graph with negative edge weights (no negative cycles) where $V = 4{,}000$ and $E = 12{,}000$, how do the operation counts of Floyd-Warshall ($O(V^3)$) and Johnson's Algorithm ($O(VE + V(V+E)\log V)$) compare?

Key Takeaways

Floyd-Warshall relaxes paths by expanding the allowed set of intermediate vertices {0..k}; therefore k must always be the outermost loop.
Adding a uniform constant +C to every edge alters shortest paths because paths with more edges are penalized more heavily.
Johnson's reweighting w_h(u, v) = w(u, v) + h(u) - h(v) telescopes along every u->v path to shift all path weights by the same constant h(u) - h(v), preserving shortest paths while making all edge weights non-negative.
After Floyd-Warshall finishes, vertex u belongs to a negative cycle if and only if dist[u][u] < 0.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue