Context:FIT2004_MOC · all-pair shortest distance on a sparse graph with negative edges — one Bellman-Ford makes every weight non-negative, then ∣V∣ runs of Dijkstra; the sparse counterpart to Floyd-WarshallParent Framework:Graph
Quick Revision
🎯 Objective: potentials p[u]=Bellman-Ford distance from a weight-0 super source ➔ reweight w′(u,v)=p[u]−p[v]+w(u,v)≥0 ➔ ∣V∣× Dijkstra ➔ undo with d(u,v)=d′(u,v)−p[u]+p[v].
📦 Core Components: ① super source + Bellman-Ford O(VE) | ② reweight O(E) | ③ V× Dijkstra O(VElogV) | ④ undo O(V2).
⚡ Key Constraint: the shift must be per vertex, not per edge — adding a constant to every edge (applied P1) charges long paths more than short ones; potentials telescope, so every u⇝v path moves by the same p[u]−p[v]. No negative cycle allowed — step ① detects one and stops.
📝 How It Works
1. Why a Constant Shift Fails (applied P1)
The tempting repair ➔ subtract the minimum weight wmin from every edge, run Dijkstra, add it back.
The flaw ➔ a k-edge path moves by k∣wmin∣ — the shift depends on edge count, so a many-edge shortest path is overtaken by a few-edge rival.
Counterexample recipe ➔ a shortest path with more edges than an alternative ➔ worked below.
2. Potentials Make Every Edge Non-Negative (applied P7a)
Definition ➔ p[u]=d[u], the shortest distance to u from a fixed source.
Claim ➔ w′(u,v)=p[u]−p[v]+w(u,v)≥0 for every edge (u,v).
Proof = the triangle inequality ➔ d[u]+w(u,v)≥d[v], else s⇝u→v beats the shortest path to v — contradiction. Subtract d[v] from both sides. ∎
3. Computing Valid Potentials (applied P7b)
An arbitrary source almost works ➔ a vertex it cannot reach gets p=∞, and w′ becomes undefined.
Fix: the super source ➔ add q with a weight-0 edge to every vertex and run Bellman-Ford from q ⟹ every p[v]≤0 is finite — the same move as Bellman-Ford §5.
Free safety check ➔ that run’s ∣V∣-th pass reports any negative cycle ⟹ stop; no all-pair answer exists.
Endpoint-only offset ➔ p[v1]−p[vk] is the same for everyv1⇝vk path, so the ranking of paths is unchanged ⟹ a shortest path under w′ is a shortest path under w. ∎
Undo ➔ d(u,v)=d′(u,v)−p[u]+p[v].
5. Assembling the Algorithm (applied P7d)
The pipeline ➔ ① potentials by super source + Bellman-Ford ② reweight every edge ③ Dijkstra from every vertex on w′≥0 ④ undo the shift on each finite answer.
Bellman-Ford runs once, Dijkstra V times ➔ negative weights exist only before step ②; after it Dijkstra’s precondition holds, so the expensive algorithm pays once and the cheap one does the repetition.
⚙️ Core Implementation
🔹 Johnson’s all-pair shortest distance
Code
def johnson(n, edges): # vertices 0..n-1, edges (u, v, w) # 1. potentials: super source q = n, weight-0 edge to every vertex O(VE) q = n aug = edges + [(q, v, 0) for v in range(n)] p, _ = bellman_ford(n + 1, aug, q) # raises on a negative cycle # 2. reweight: w' = p[u] - p[v] + w >= 0 O(E) adj = [[] for _ in range(n)] for (u, v, w) in edges: adj[u].append((v, p[u] - p[v] + w)) # 3 + 4. Dijkstra from every vertex, then undo the shift O(VE log V) dist = [[INF] * n for _ in range(n)] for u in range(n): d_prime = dijkstra(adj, u) for v in range(n): if d_prime[v] != INF: dist[u][v] = d_prime[v] - p[u] + p[v] return dist
💡 Common Mistake:Returning d′ ➔ the reweighted distances are not the answer; every finite entry needs −p[u]+p[v].
Applied Exercise — the constant shift breaks it (applied P1)
wmin=−5⇒3-edge path:1-edge path:Dijkstra returnsadd 5:s→a(0),a→d(2),d→b(1),s→b(1)0+2+1=3(moved by 3×5=15)1(moved by only 5)s→b⇒1−5=−4=−12
Final Extracted Output: wrong answer −4 — the shift penalised the 3-edge shortest path three times as hard as its 1-edge rival.
Manual Execution Trace — potentials fix it (same graph)
Super source q with weight-0 edges to s,a,d,b; Bellman-Ford from q, then reweight.
Edge
p[u]
p[v]
w
w′=p[u]−p[v]+w
s→a
0
−5
−5
0
a→d
−5
−8
−3
0
d→b
−8
−12
−4
0
s→b
0
−12
−4
8
Final Extracted Output: Dijkstra from s takes s→a→d→b at d′=0 over the direct edge at 8 ⟹ d(s,b)=0−p[s]+p[b]=0−0+(−12)=−12 ✓.
Read the zeros ➔ edges on the super source’s shortest-path tree make the triangle inequality tight, so they reweight to exactly 0; only slack edges stay positive.
⚠️ Common Mistakes
💡 Shifting every edge by a constant ➔ the shift scales with edge count (§1); only per-vertex potentials telescope.
💡 Potentials from an ordinary source ➔ unreachable vertices get p=∞; use the weight-0 super source.
💡 Quoting O(V2logV) unconditionally ➔ that is the sparse instantiation; the general bound is O(VElogV), and on a dense graph Floyd-Warshall wins.
💡 “Johnson’s handles negative cycles” ➔ no all-pair answer exists then; step ① detects and reports, exactly as Bellman-Ford does.
🧠 Active Recall
Adding a constant to every edge and adding p[u]−p[v] to every edge both shift weights. Why does only one preserve shortest paths?
Answer
Short answer: the potential shift depends only on a path’s endpoints; the constant shift depends on its number of edges.
Why:Telescoping ➔ w′(P)=w(P)+p[v1]−p[vk], identical for every v1⇝vk path, so the order of paths is preserved. Constant shift ➔ w′(P)=w(P)+kc with k varying by path, so few-edge paths gain ground ➔ applied P1’s s→b beats s→a→d→b after the shift.
Why is every reweighted edge ≥0, and why must the potentials come from a super source?
Answer
Short answer: the triangle inequalityd[u]+w(u,v)≥d[v] rearranges to w′(u,v)≥0; a super source makes every d[v] finite.
Why:The inequality is a shortest-path optimality fact ➔ if it failed, s⇝u→v would beat d[v]. An ordinary source leaves gaps ➔ an unreachable v has d[v]=∞ and p[u]−p[v] is undefined. Weight-0 edges from q add no shortcut between real vertices ➔ q has no incoming edge, so it never sits inside an original u⇝v path.
Johnson's uses both Bellman-Ford and Dijkstra. Why that split, and what would V Bellman-Fords cost instead?
Answer
Short answer: Bellman-Ford is needed only while negative weights exist — once, for the potentials; afterwards Dijkstra is legal and cheaper per source. V Bellman-Fords cost O(V2E).
Why:Pay the expensive algorithm once ➔ O(VE) buys non-negative w′. Repeat the cheap one ➔ V⋅O(ElogV). The comparison ➔ O(VElogV) vs O(V2E): the logV replaces a factor of V.