Skip to content
Part IA Lent Term

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 hh, uses hh to reweight all edges to non-negative values, then runs Dijkstra V|V| 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: w^(u,v)=w(u,v)+h(u)h(v)\hat{w}(u, v) = w(u, v) + h(u) - h(v).

Why Naive Reweighting Fails

Adding a fixed constant bb to every edge weight makes all edges non-negative, but changes which paths are shortest. If two paths from AA to BB have 2 edges and 5 edges respectively, the 5-edge path accumulates 5b5b vs 2b2b, so the ranking can change. Johnson’s vertex-potential reweighting avoids this.

The Reweighting Function

Given a function h:VRh: V \to \mathbb{R}, define the reweighted edge weight:

w^(u,v)=w(u,v)+h(u)h(v)\hat{w}(u, v) = w(u, v) + h(u) - h(v)

Property 1 (shortest-path preservation): For any path p=v1v2vkp = v_1 \to v_2 \to \cdots \to v_k:

w^(p)=i=1k1(w(vi,vi+1)+h(vi)h(vi+1))=w(p)+h(v1)h(vk)\hat{w}(p) = \sum_{i=1}^{k-1} (w(v_i, v_{i+1}) + h(v_i) - h(v_{i+1})) = w(p) + h(v_1) - h(v_k)

The terms telescope: h(vi)h(v_i) and h(vi+1)-h(v_{i+1}) cancel for interior vertices. The difference h(v1)h(vk)h(v_1) - h(v_k) depends only on the endpoints, not the path. Therefore w^\hat{w} preserves the relative ordering of paths between any fixed pair of vertices: a shortest path under ww is also a shortest path under w^\hat{w}.

Property 2 (non-negative weights): If h(v)=δ(s,v)h(v) = \delta(s, v) (the true shortest distance from some source ss), then by the triangle inequality δ(s,v)δ(s,u)+w(u,v)\delta(s, v) \le \delta(s, u) + w(u, v), so:

w^(u,v)=w(u,v)+h(u)h(v)0\hat{w}(u, v) = w(u, v) + h(u) - h(v) \ge 0

Algorithm

  1. Augment GG to GG': add a new vertex ss connected to every vVv \in V with weight 0. GG' has no new negative cycles.
  2. Run Bellman-Ford on GG' from ss. If it detects a negative cycle, report failure. Otherwise, set h(v)=δ(s,v)h(v) = \delta(s, v) for all vv.
  3. Reweight every original edge: w^(u,v)=w(u,v)+h(u)h(v)\hat{w}(u, v) = w(u, v) + h(u) - h(v). Now all edges have non-negative weight.
  4. For each vertex uVu \in V, run Dijkstra on the reweighted graph to get δ^(u,v)\hat{\delta}(u, v) for all vv.
  5. Recover true distances: δ(u,v)=δ^(u,v)h(u)+h(v)\delta(u, v) = \hat{\delta}(u, v) - h(u) + h(v).

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 V={1,2,3}V = \{1,2,3\} and edges: 121 \to 2 (weight 2-2), 131 \to 3 (weight 44), 232 \to 3 (weight 11). This has a negative edge 121 \to 2 but no negative cycles.

Step 1: Augment with ss, edges s1,s2,s3s \to 1, s \to 2, s \to 3 (weight 0).

Step 2: Bellman-Ford from ss gives h(1)=0h(1)=0, h(2)=0h(2)=0, h(3)=0h(3)=0 (there are no paths to improve, since ss edges are 0-weight). Actually, with the original edges present: h(1)=0h(1)=0, h(2)=2h(2)=-2, h(3)=1h(3)=-1 (path s123s \to 1 \to 2 \to 3 costs 02+1=10-2+1=-1, or s13s \to 1 \to 3 costs 44).

Step 3: Reweight:

  • w^(1,2)=2+0(2)=0\hat{w}(1,2) = -2 + 0 - (-2) = 0
  • w^(1,3)=4+0(1)=5\hat{w}(1,3) = 4 + 0 - (-1) = 5
  • w^(2,3)=1+(2)(1)=0\hat{w}(2,3) = 1 + (-2) - (-1) = 0

All reweighted edges are non-negative.

Step 4: Run Dijkstra from 1: δ^(1,2)=0\hat{\delta}(1,2)=0, δ^(1,3)=0\hat{\delta}(1,3)=0 (via 1231 \to 2 \to 3).

Step 5: Recover: δ(1,2)=00+(2)=2\delta(1,2) = 0 - 0 + (-2) = -2, δ(1,3)=00+(1)=1\delta(1,3) = 0 - 0 + (-1) = -1.

Results match the original graph (path 1231 \to 2 \to 3 costs 2+1=1-2+1=-1).

Running Time

  • Step 1: Θ(V)\Theta(|V|).
  • Step 2: Θ(VE)\Theta(|V|\cdot|E|) (Bellman-Ford).
  • Step 3: Θ(E)\Theta(|E|).
  • Step 4: V|V| calls to Dijkstra. With binary heap: O(V(V+E)logV)=O(VElogV)O(|V| \cdot (|V|+|E|)\log|V|) = O(|V| \cdot |E| \log |V|). With Fibonacci heap: O(V2logV+VE)O(|V|^2 \log |V| + |V| \cdot |E|).
  • Step 5: Θ(V2)\Theta(|V|^2).

Total: O(V2logV+VE)O(|V|^2 \log |V| + |V| \cdot |E|) with Fibonacci heap, or O(VElogV)O(|V| \cdot |E| \log |V|) with binary heap.

Comparison with Floyd-Warshall

AlgorithmTimeBest for
JohnsonO(V2logV+VE)O(V^2 \log V + VE)Sparse graphs
Floyd-WarshallΘ(V3)\Theta(V^3)Dense graphs

Johnson wins when E=o(V2)|E| = o(|V|^2). When E=Θ(V2)|E| = \Theta(|V|^2), Floyd-Warshall’s simpler implementation and smaller constant factor make it preferable.

Summary

StepOperationTime
AugmentAdd dummy ssO(V)O(V)
Bellman-FordCompute h(v)h(v)O(VE)O(VE)
Reweightw^=w+h(u)h(v)\hat{w} = w + h(u) - h(v)O(E)O(E)
Dijkstra × VVAll-pairsO(VElogV)O(V \cdot E \log V)
Un-reweightD[u][v]+h(u)h(v)D[u][v] + h(u) - h(v)O(V2)O(V^2)

Past paper questions: y2024p1q9 (reweighting, relaxed costs, opportunities).