Johnson's algorithm
All-pairs shortest paths with negative edges: Bellman–Ford makes the weights non-negative, then Dijkstra runs from every node.
Dijkstra from every node gives all-pairs shortest paths in O(V · E log V); for sparse graphs that beats
Floyd–Warshall (V³). But Dijkstra fails with negative edges. Johnson changes the weights so none is negative while
the shortest paths stay the same.
- Time:
O(V · E log V) - Space:
O(V²) - CLRS, section 23.3
The same graph as Floyd–Warshall, with two negative edges; the potentials come out as h = (0, −1, −5, 0, −4) and the answer matches Floyd–Warshall.
import heapqINF = float("inf")def johnson(graph, src):# 1. Bellman-Ford from a new node q with a 0-edge to every node.h = {v: 0 for v in graph}for round_no in range(len(graph)):changed = Falsefor u in graph:for v, w in graph[u]:if h[u] + w < h[v]:h[v] = h[u] + wchanged = Trueif not changed:breakfor u in graph:for v, w in graph[u]:if h[u] + w < h[v]:return None# 2. Dijkstra from every node on the reweighted edges.dist = {}for s in graph:d = dijkstra(graph, h, s)dist[s] = {v: d[v] - h[s] + h[v] for v in graph}return distdef dijkstra(graph, h, s):d = {v: INF for v in graph}d[s] = 0heap = [(0, s)]while heap:du, u = heapq.heappop(heap)if du > d[u]:continuefor v, w in graph[u]:nd = du + w + h[u] - h[v]if nd < d[v]:d[v] = ndheapq.heappush(heap, (nd, v))return d
Start
- Current node
(u) - Shortest-path tree so far
- Edge being checked
Local variables
Orange = changed in this step
graph dict
- '1': [('2', 3), ('3', 8), ('5', -4)]
- '2': [('4', 1), ('5', 7)]
- '3': [('2', 4)]
- '4': [('1', 2), ('3', -5)]
- '5': [('4', 6)]
Call stack
- johnson()line 6
How the work grows with input size
Average of 5 random graphs per n (fixed seeds). Hollow marker = the input on screen.
- reweight
- current input (n=5)
Show as a table
| n | reweight |
|---|---|
| 4 | 8.4 |
| 8 | 38 |
| 12 | 54.8 |
Explanation
The idea
Dijkstra from every node gives all-pairs shortest paths in O(V · E log V); for sparse graphs that beats
Floyd–Warshall (V³). But Dijkstra fails with negative edges. Johnson changes the weights so none is negative while
the shortest paths stay the same.
Reweighting
Give every node a potential h[v] and change each edge's weight to:
w'(u, v) = w(u, v) + h[u] − h[v]
The length of every path from s to t changes by the same constant h[s] − h[t], so the order of the paths does
not change.
Where do the potentials come from?
Add a new node q with a 0-weight edge to everyone and run Bellman–Ford from q: h[v] = δ(q, v). By the triangle
inequality h[v] ≤ h[u] + w, so w' ≥ 0. If Bellman–Ford finds a negative cycle, there is no answer.
What to watch in the trace
- First the potentials (
hin the memory panel) are built; then Dijkstra runs from every node, and the numbers by the nodes are Dijkstra's distances in the new weights.