Johnson's algorithm
DefinitionJohnson's algorithm#✓fully proved
Let \(E\) be a list of directed edges on the vertices \(\{0, \dots, n-1\}\) with integer weights. Johnson's algorithm adds a new vertex \(s\) with an edge of weight \(0\) to every original vertex and runs Bellman–Ford from \(s\). If Bellman–Ford reports a negative cycle, the algorithm returns no table. Otherwise let \(h(v)\) be the Bellman–Ford distance of \(v\) (taken to be \(0\) if \(v\) is unreached). Each edge from \(u\) to \(v\) of weight \(w\) gets the new weight \(\max(0, w + h(u) - h(v))\). For each original vertex \(u\) the algorithm runs Dijkstra from \(u\) on the original vertices, where the weight from \(x\) to \(y\) is the least new weight of an edge from \(x\) to \(y\). It returns the table whose entry at \((u, v)\) is \(\infty\) if Dijkstra does not reach \(v\), and \(d'(u, v) + h(v) - h(u)\) otherwise, where \(d'(u, v)\) is the Dijkstra distance.
The cost counts the selected construction, scan, relaxation and decoding actions, not full RAM or bit complexity.
public def johnson {n : Nat} (edges : List (Edge n)) :
TimeM Nat (Option (Fin n → Fin n → WithTop Int)) := do
let augmented ← augment edges
let run := bellmanFord augmented (Fin.last n)
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
match result with
| .negativeCycle _ => pure none
| .distances distances => do
let potential ← readPotentials distances
let reweighted ← reweightEdges potential augmented
let prepared ← prepare edges reweighted
let rows ← buildVector n (runRow prepared potential)
pure (some fun source target => (rows.get source).get target)
public structure Edge (n : Nat) where
/-- The source vertex. -/
source : Fin n
/-- The target vertex. -/
target : Fin n
/-- The integer edge weight. -/
weight : Int
deriving DecidableEq
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:59
private def augment {n : Nat} (edges : List (Edge n)) :
TimeM Nat (List (Edge (n + 1))) := do
let zeros ← sourceEdges (List.finRange n)
liftEdges zeros edges
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:47
public def bellmanFord {n : Nat} (edges : List (Edge n)) (source : Fin n) :
TimeM Cost (Result edges source) :=
Internal.bellmanFord edges source
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Correctness.lean:964
private def readPotentials {n : Nat} (result : Distances n) : TimeM Nat (Vector Int n) :=
buildVector n fun vertex => do
TimeM.tick 1
pure ((result.distance.get vertex).getD 0)
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:60
private def reweightEdges {n : Nat} (potential : Vector Int n) :
List (Edge n) → TimeM Nat (List (Edge n))
| [] => pure []
| edge :: rest => do
TimeM.tick 1
let tail ← reweightEdges potential rest
pure (⟨edge.source, edge.target,
edge.weight + potential.get edge.source - potential.get edge.target⟩ :: tail)
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:65
private def prepare {n : Nat} (edges : List (Edge n))
(reweighted : List (Edge (n + 1))) : TimeM Nat (Prepared n) := do
let empty ← emptyPrepared n
let inserted ← insertEntries (edges.zip reweighted) empty
let buckets ← buildVector n fun vertex => reverseBucket (inserted.buckets.get vertex) []
pure ⟨buckets, inserted.minima⟩
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:114
private def buildVector {α : Type} :
(n : Nat) → (Fin n → TimeM Nat α) → TimeM Nat (Vector α n)
| 0, _ => pure #v[]
| n + 1, action => do
let previous ← buildVector n (fun vertex => action vertex.castSucc)
let last ← action (Fin.last n)
pure (previous.push last)
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:52
private def runRow {n : Nat} (prepared : Prepared n) (potential : Vector Int (n + 1))
(source : Fin n) : TimeM Nat (Vector (WithTop Int) n) := do
TimeM.tick 1
let run := prepared.graph.dijkstra prepared.weight source
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
buildVector n fun target => do
TimeM.tick 1
pure (match result.distance.get target with
| none => ⊤
| some distance => ((distance : Int) + potential.get target.castSucc -
potential.get source.castSucc : Int))
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:130
Uses 12 · Used by 4
TheoremJohnson's algorithm fails exactly on reachable negative cycles#✓fully proved
For every list \(E\) of directed edges on the vertices \(\{0, \dots, n-1\}\) with integer weights, Johnson's algorithm on \(E\) returns no table if and only if there is a vertex \(s\) from which a negative cycle of \(E\) is reachable.
public theorem johnson_eq_none_iff {n : Nat} (edges : List (Edge n)) :
(johnson edges).ret = none ↔ ∃ source : Fin n, HasReachableNegativeCycle edges source
public structure Edge (n : Nat) where
/-- The source vertex. -/
source : Fin n
/-- The target vertex. -/
target : Fin n
/-- The integer edge weight. -/
weight : Int
deriving DecidableEq
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:59
public def johnson {n : Nat} (edges : List (Edge n)) :
TimeM Nat (Option (Fin n → Fin n → WithTop Int)) := do
let augmented ← augment edges
let run := bellmanFord augmented (Fin.last n)
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
match result with
| .negativeCycle _ => pure none
| .distances distances => do
let potential ← readPotentials distances
let reweighted ← reweightEdges potential augmented
let prepared ← prepare edges reweighted
let rows ← buildVector n (runRow prepared potential)
⋯
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:145
@[expose] public def HasReachableNegativeCycle {n : Nat}
(edges : List (Edge n)) (source : Fin n) : Prop :=
Nonempty (ReachableNegativeCycle edges source)
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:263
Uses 19
TheoremCorrectness of Johnson's algorithm#✓fully proved
Let \(E\) be a list of directed edges on the vertices \(\{0, \dots, n-1\}\) with integer weights, and suppose Johnson's algorithm on \(E\) returns a table \(d\). Then for all vertices \(s\) and \(t\), \(d(s, t) = \infty\) if and only if \(t\) is not reachable from \(s\) along edges of \(E\). Moreover, if \(d(s, t)\) is an integer \(x\), then some weighted path in \(E\) from \(s\) to \(t\) has weight \(x\), and every weighted path in \(E\) from \(s\) to \(t\) has weight at least \(x\).
public theorem johnson_distance_spec {n : Nat} (edges : List (Edge n))
(distance : Fin n → Fin n → WithTop Int) (run : (johnson edges).ret = some distance)
(source target : Fin n) :
(distance source target = ⊤ ↔ ¬(toDirectedAdjacency edges).Reachable source target) ∧
∀ value : Int, distance source target = (value : WithTop Int) →
(∃ path, IsWeightedPath edges source target path ∧ pathWeight path = value) ∧
∀ path, IsWeightedPath edges source target path → value ≤ pathWeight path
public structure Edge (n : Nat) where
/-- The source vertex. -/
source : Fin n
/-- The target vertex. -/
target : Fin n
/-- The integer edge weight. -/
weight : Int
deriving DecidableEq
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:59
public def johnson {n : Nat} (edges : List (Edge n)) :
TimeM Nat (Option (Fin n → Fin n → WithTop Int)) := do
let augmented ← augment edges
let run := bellmanFord augmented (Fin.last n)
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
match result with
| .negativeCycle _ => pure none
| .distances distances => do
let potential ← readPotentials distances
let reweighted ← reweightEdges potential augmented
let prepared ← prepare edges reweighted
let rows ← buildVector n (runRow prepared potential)
⋯
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:145
@[expose] public def toDirectedAdjacency {n : Nat}
(edges : List (Edge n)) : DirectedAdjacency (Fin n) where
vertices := List.finRange n
vertices_nodup := List.nodup_finRange n
vertices_complete := List.mem_finRange
adjacency source :=
(edges.filter fun edge => decide (edge.source = source)).map Edge.target
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:69
@[expose] public def IsWeightedPath {n : Nat} (edges : List (Edge n)) (source target : Fin n)
(path : List (Edge n)) : Prop :=
(∀ edge ∈ path, edge ∈ edges) ∧
path.IsChain (fun left right => left.target = right.source) ∧
match path with
| [] => source = target
| first :: rest =>
first.source = source ∧ (first :: rest).getLast?.map Edge.target = some target
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:87
@[expose] public def pathWeight {n : Nat} (path : List (Edge n)) : Int :=
(path.map Edge.weight).sum
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:83
Uses 32
TheoremExact cost of a successful run of Johnson's algorithm#✓fully proved
Let \(E\) be a list of \(m\) directed edges on the vertices \(\{0, \dots, n-1\}\) with integer weights. If Johnson's algorithm on \(E\) returns a table, then its counted cost is exactly \(n^3 + nm + (n+1)(m+n) + 2n^2 + 4m + 5n + 1\).
public theorem johnson_time_of_success {n : Nat} (edges : List (Edge n))
(distance : Fin n → Fin n → WithTop Int) (run : (johnson edges).ret = some distance) :
(johnson edges).time = n^3 + n*edges.length + (n+1)*(edges.length+n) +
2*n^2 + 4*edges.length + 5*n + 1
public structure Edge (n : Nat) where
/-- The source vertex. -/
source : Fin n
/-- The target vertex. -/
target : Fin n
/-- The integer edge weight. -/
weight : Int
deriving DecidableEq
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:59
public def johnson {n : Nat} (edges : List (Edge n)) :
TimeM Nat (Option (Fin n → Fin n → WithTop Int)) := do
let augmented ← augment edges
let run := bellmanFord augmented (Fin.last n)
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
match result with
| .negativeCycle _ => pure none
| .distances distances => do
let potential ← readPotentials distances
let reweighted ← reweightEdges potential augmented
let prepared ← prepare edges reweighted
let rows ← buildVector n (runRow prepared potential)
⋯
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:145
Uses 17
TheoremCost bound for Johnson's algorithm#✓fully proved
For every list \(E\) of \(m\) directed edges on the vertices \(\{0, \dots, n-1\}\) with integer weights, the counted cost of Johnson's algorithm on \(E\), including the case where it extracts a negative cycle, is at most \(n^3 + nm + (n+1)(m+n) + 2n^2 + 4m + 5n + 1 + 3(n+1)\).
public theorem johnson_time_le {n : Nat} (edges : List (Edge n)) :
(johnson edges).time ≤ n^3 + n*edges.length + (n+1)*(edges.length+n) +
2*n^2 + 4*edges.length + 5*n + 1 + 3*(n+1)
public structure Edge (n : Nat) where
/-- The source vertex. -/
source : Fin n
/-- The target vertex. -/
target : Fin n
/-- The integer edge weight. -/
weight : Int
deriving DecidableEq
CSLibExt/Algorithms/Lean/Graph/ShortestPath/BellmanFord/Basic.lean:59
public def johnson {n : Nat} (edges : List (Edge n)) :
TimeM Nat (Option (Fin n → Fin n → WithTop Int)) := do
let augmented ← augment edges
let run := bellmanFord augmented (Fin.last n)
let result ← ({ ret := run.ret, time := run.time.total } : TimeM Nat _)
match result with
| .negativeCycle _ => pure none
| .distances distances => do
let potential ← readPotentials distances
let reweighted ← reweightEdges potential augmented
let prepared ← prepare edges reweighted
let rows ← buildVector n (runRow prepared potential)
⋯
CSLibExt/Algorithms/Lean/Graph/ShortestPath/Johnson/Basic.lean:145
Uses 18
Loading dependency explorer…
The dependency explorer needs JavaScript and its graph data.