A Systematic Approach to Algorithms

Vijay K. Garg · The University of Texas at Austin

Chapter 8. The Shortest Path Problem

Single-source and all-pairs distances via greedy fixing, edge relaxation, and reweighting.

This page: LLP forms. View classical forms »

Setting

Given a weighted directed graph $(V, E, w)$ with $n = |V|$ and $m = |E|$, the single-source shortest-path (SSSP) problem asks, for a designated source $v_0$ and every vertex $x$, the minimum total weight $\text{cost}[x]$ of any directed $v_0 \to x$ path. The all-pairs shortest-path (APSP) problem asks the $n \times n$ matrix of shortest-path distances between every pair of vertices.

Dijkstra's Algorithm

For graphs with non-negative edge weights, the greedy idea works: among the vertices not yet finalised, the one with the smallest tentative distance must be at its true shortest distance. Dijkstra repeatedly extracts that vertex (using a min-heap keyed on the tentative distance), fixes it, and relaxes its outgoing edges: $d[k] := \min(d[k], d[j] + w(j, k))$. Sequential running time: $O((n + m) \log n)$ with a binary heap, $O(m + n \log n)$ with a Fibonacci heap. Correctness rests on edge non-negativity: extending any path can only make it longer, so the smallest tentative distance is final.

BellmanFord Algorithm

Removes the non-negativity restriction. BellmanFord performs $n - 1$ passes over every edge, relaxing each. After $i$ passes, $d[x]$ is the shortest-path distance among paths using at most $i$ edges; after $n - 1$ passes, all simple shortest paths have been captured. A final $n$-th pass detects negative cycles: any edge that still relaxes witnesses a negative-weight cycle reachable from the source. Sequential running time: $\Theta(nm)$.

FloydWarshall Algorithm

A clean dynamic-programming APSP algorithm. Let $d^{(k)}[i][j]$ be the shortest-path distance from $i$ to $j$ using only $\{v_1, \ldots, v_k\}$ as intermediate vertices. The recurrence $d^{(k)}[i][j] = \min(d^{(k-1)}[i][j], d^{(k-1)}[i][k] + d^{(k-1)}[k][j])$ fits in a triple loop over $k, i, j$ with $\Theta(n^3)$ total work and $\Theta(n^2)$ space (the $k$ index can be updated in-place). Negative edges are allowed, but no negative cycles.

Johnson's Algorithm

For sparse graphs, FloydWarshall's $\Theta(n^3)$ is wasteful. Johnson's algorithm combines BellmanFord and Dijkstra: first add a virtual source $s^\star$ with zero-weight edges to every vertex and run BellmanFord to compute prices $\text{price}[v] = -\text{dist}(s^\star, v)$. Reweighting each edge $(u, v)$ to $w'(u, v) = w(u, v) + \text{price}[v] - \text{price}[u]$ yields an equivalent graph (same shortest-path structure) with all non-negative weights. Now run Dijkstra from each vertex on the reweighted graph and undo the shift via $D[u, v] = \text{dist}'(u, v) + \text{price}[u] - \text{price}[v]$. Total time: $O(mn + n^2 \log n)$ — faster than $\Theta(n^3)$ when $m = o(n^2 / \log n)$.

The LLP perspective

All four algorithms admit lattice-linear reformulations on the distance vector $d[1..n]$:

Three of these ship as runnable LL programs below. LLP-Dijkstra is shown as a code listing only (it relies on a priority-queue runtime that is not yet wired into the in-page stepper). LLP-BellmanFord and LLP-Johnson have full step-through demos.

LLP-Dijkstra

LLP form of Dijkstra. The forbidden index is whichever non-fixed vertex currently sits at the head of a priority queue keyed on $d[j]$; advancing it fixes the vertex and (in a full front-end) relaxes its outgoing edges. The setMode(PriorityQueue) clause tells the LL runtime to maintain a min-heap of candidate forbidden indices; setPriority(d[j], min) keys the heap on the current distance.

Time complexity: $O((n + m) \log n)$ with a binary heap, where $n$ is the number of vertices and $m$ is the number of edges.

int[] LLPDijkstra(int[] dIn) {
  int[]     d     = infinity;
  boolean[] fixed = false;

  setMode(PriorityQueue);
  setPriority(d[j], min);

  forbidden (j) : !fixed[j]  =>
    advance : fixed[j] = true;
  return d;
}

LLP-BellmanFord

Single-source shortest paths with possibly-negative edge weights (no negative cycle). Vertex $j$ is forbidden when some predecessor $i$ would relax it: $d[i] + w(i, j) < d[j]$; the advance lowers $d[j]$ to the minimum over all predecessors. Lattice-linear because each lowering is monotone in the surrounding state.

Time complexity: $O(n \cdot m)$, where $n$ is the number of vertices and $m$ is the number of edges.

int[] LLPBellmanFord(set<int>[] pre, int[][] w) {
  int[] d = infinity;
  d[0] = 0;
  forbidden (j) :
    exists i in pre[j] : d[j] > d[i] + w[i][j]
  =>
    advance :
      forall k in [0..n] :
        d[k] = min(d[k], min i in pre[k] : d[i] + w[i][k]);
  return d;
}

LLP-Johnson

Computes a non-negative price vector $p$ that, after reweighting each edge by $w'(i, j) = w(i, j) + p[j] - p[i]$, leaves all weights non-negative — the preprocessing step of Johnson's algorithm. The forbidden / advance pair is the dual of LLP-BellmanFord with $\max$ replacing $\min$ (because prices are maximised, distances are minimised).

Time complexity: $O(n m + n^2 \log n)$ (BellmanFord preprocessing plus $n$ Dijkstra runs), where $n$ is the number of vertices and $m$ is the number of edges.

int[] LLPJohnson(set<int>[] pre, int[][] w) {
  int[] p = 0;
  forbidden (j) :
    exists i in pre[j] : p[j] < p[i] - w[i][j]
  =>
    advance :
      forall k in [0..n] :
        p[k] = max(p[k], max i in pre[k] : p[i] - w[i][k]);
  return p;
}

Constrained shortest path

Two common side conditions on shortest paths are a hop budget (no more than $k$ edges along the chosen path) and a resource budget (the total of a secondary edge cost stays within $B$). Each is expressed as a one-rule constraint program operating on an augmented state vector — $h[j]$ is the hop count along the relaxation chain that established $G[j]$; $\rho[j]$ is the cumulative resource. The Lattice-Linear Language's predicate-conjunction operator $\&\&$ composes the constraint with LLP-Dijkstra.

HopBound

Enforces $h[j] \leq k$. Forbidden when some vertex has more than $k$ hops on its current shortest-path chain. Advance halts ``infeasible'' (returns null), since no further relaxation can shorten the hop count without first improving $G[j]$. Composed with LLP-Dijkstra-Hops: $[\,\textsf{LLP-Dijkstra-Hops}\,\&\&\,\textsf{HopBound}\,]$.

Time complexity: $O((n + m) \log n)$, identical to LLP-Dijkstra up to constant factors.

int[] HopBound(int[] h, int k, int[] G) {
  forbidden (j) : h[j] > k =>
    advance : return null;
  return G;
}

ResourceBound

Enforces $\rho[j] \leq B$. Forbidden when the resource consumption of some vertex's relaxation chain exceeds the budget. Advance halts ``infeasible''. Composed with LLP-Dijkstra-Res: $[\,\textsf{LLP-Dijkstra-Res}\,\&\&\,\textsf{ResourceBound}\,]$. The single-state formulation may miss a feasible path of lower cost; a Pareto-frontier extension is sketched in the textbook and runs in pseudo-polynomial time $O((n + m) B \log n)$ for integer resources.

Time complexity: $O((n + m) \log n)$ for the single-state variant.

int[] ResourceBound(int[] rho, int B, int[] G) {
  forbidden (j) : rho[j] > B =>
    advance : return null;
  return G;
}

LLP predicates for this chapter

Each LLP program in this chapter defines a state vector $G$ and a forbidden predicate; the algorithm runs until no $j$ is forbidden. The table below lists, for each algorithm, what $G[i]$ represents and the negation of the forbidden clause from the matching .llp source.

Algorithm $G[j]$ Negation of the forbidden clause
Dijkstra $d[j]$ — distance to vertex $j$ $\forall (i, j) \in E:\ d[j] \leq d[i] + w[i, j]$
BellmanFord $d[j]$ — distance to vertex $j$ $\forall (i, j) \in E:\ d[j] \leq d[i] + w[i, j]$
FloydWarshall $G[i, j]$ — distance from $i$ to $j$ $\forall i, j, k:\ G[i, j] \leq G[i, k] + G[k, j]$
Johnson $p[j]$ — price of vertex $j$ $\forall (i, j) \in E:\ p[j] \geq p[i] - w[i, j]$

Looking ahead

Shortest-path machinery underpins Chapter 9 (minimum spanning trees), where Prim's algorithm shares Dijkstra's "fix-the-closest-frontier-vertex" structure but uses a different relaxation rule. Johnson's reweighting reappears in network-flow chapters as the key idea behind successive-shortest-paths and primal-dual methods. The $\Delta$-stepping algorithm (covered in the book's later sections) bridges LLP-Dijkstra and LLP-BellmanFord via a tunable bucket width.