Johnson's Algorithm
Overview
Johnson’s algorithm solves the all-pairs shortest paths (APSP) problem for sparse graphs with possibly negative edge weights. It runs Bellman-Ford once to compute a potential function , uses to reweight all edges to non-negative values, then runs Dijkstra times. The original distances are recovered by subtracting the potential difference.
The key insight: adding a constant to every edge weight does not preserve shortest paths (longer paths accumulate more bias). Instead, the bias must depend on the endpoints: .
Why Naive Reweighting Fails
Adding a fixed constant to every edge weight makes all edges non-negative, but changes which paths are shortest. If two paths from to have 2 edges and 5 edges respectively, the 5-edge path accumulates vs , so the ranking can change. Johnson’s vertex-potential reweighting avoids this.
The Reweighting Function
Given a function , define the reweighted edge weight:
Property 1 (shortest-path preservation): For any path :
The terms telescope: and cancel for interior vertices. The difference depends only on the endpoints, not the path. Therefore preserves the relative ordering of paths between any fixed pair of vertices: a shortest path under is also a shortest path under .
Property 2 (non-negative weights): If (the true shortest distance from some source ), then by the triangle inequality , so:
Algorithm
- Augment to : add a new vertex connected to every with weight 0. has no new negative cycles.
- Run Bellman-Ford on from . If it detects a negative cycle, report failure. Otherwise, set for all .
- Reweight every original edge: . Now all edges have non-negative weight.
- For each vertex , run Dijkstra on the reweighted graph to get for all .
- Recover true distances: .
Pseudocode:
JOHNSON(G, w):
G' = (V ∪ {s}, E ∪ {(s,v) : v ∈ V}) with w(s,v)=0
if BELLMAN-FORD(G', w, s) == FALSE:
error("negative cycle")
for each edge (u,v) in G.E:
ŵ(u,v) = w(u,v) + u.d - v.d // u.d = δ(s,u)
D = new |V|×|V| matrix
for each u in G.V:
DIJKSTRA(G, ŵ, u)
for each v in G.V:
D[u][v] = v.d - u.h + v.h // undo reweighting
return D
Worked Example
Consider a graph with and edges: (weight ), (weight ), (weight ). This has a negative edge but no negative cycles.
Step 1: Augment with , edges (weight 0).
Step 2: Bellman-Ford from gives , , (there are no paths to improve, since edges are 0-weight). Actually, with the original edges present: , , (path costs , or costs ).
Step 3: Reweight:
All reweighted edges are non-negative.
Step 4: Run Dijkstra from 1: , (via ).
Step 5: Recover: , .
Results match the original graph (path costs ).
Running Time
- Step 1: .
- Step 2: (Bellman-Ford).
- Step 3: .
- Step 4: calls to Dijkstra. With binary heap: . With Fibonacci heap: .
- Step 5: .
Total: with Fibonacci heap, or with binary heap.
Comparison with Floyd-Warshall
| Algorithm | Time | Best for |
|---|---|---|
| Johnson | Sparse graphs | |
| Floyd-Warshall | Dense graphs |
Johnson wins when . When , Floyd-Warshall’s simpler implementation and smaller constant factor make it preferable.
Summary
| Step | Operation | Time |
|---|---|---|
| Augment | Add dummy | |
| Bellman-Ford | Compute | |
| Reweight | ||
| Dijkstra × | All-pairs | |
| Un-reweight |
Past paper questions: y2024p1q9 (reweighting, relaxed costs, opportunities).