Shortest Paths
Finding the cheapest route through a weighted network is one of the most-used algorithms in computing. A single operation — relaxation — underlies them all.
╌╌╌╌
Every navigation app, every network router, every game pathfinder is solving the same problem: given a weighted graph, find the cheapest route from one place to another. BFS already solved this when every edge counts as one step. Now the edges carry weights (distances, times, costs), and we want to minimize the total weight along a path. This lesson builds the shortest-path toolkit from a single primitive shared by every algorithm in it.
The problem and its primitive
Every algorithm maintains two arrays. For each vertex , an estimate is an upper bound on , always the true distance, shrinking toward it. A predecessor records the previous vertex on the best path found so far, forming a shortest-path tree. We initialize and for every other vertex.
The one operation that updates these estimates is relaxation: testing whether going through improves our route to .
- 1if then
- 2cheaper route to v via u
- 3
Relaxation never produces an estimate below the true distance, and it can only ever lower an estimate. Every shortest-path algorithm below is a different discipline for deciding which edges to relax, and in what order. Two facts make relaxation work: the triangle inequality, and optimal substructure: any subpath of a shortest path is itself a shortest path.1 The latter is what makes greedy and dynamic-programming approaches both viable.
One relaxation step looks like this. Before, believes its best route costs ; we test the edge of weight against 's settled estimate . Since , the edge is a shortcut: drops to and is rewired to point back through .
We will trace the algorithms on this small weighted digraph. Note the negative edge of weight : it is harmless here (there is no negative cycle), but it is exactly the kind of edge that will break Dijkstra and force us toward a dynamic program.
Dijkstra's algorithm
When all edge weights are non-negative, we can be greedy. 's algorithm grows a set of vertices whose shortest distances are finalized. At each step it picks the non-finalized vertex with the smallest estimate , finalizes it, and relaxes its outgoing edges. A min-priority queue keyed by supplies the next vertex.
- 1foreach vertex do
- 2
- 3
- 4
- 5
- 6min-PQ keyed by d
- 7while do
- 8closest unfinalized
- 9u.d now final
- 10foreach adjacent to do
- 11callDecrease-Key updates Q
- 12return and
Why the greedy choice is correct. Correctness is framed around a
single loop invariant: at the start of each iteration of the while loop,
every finalized vertex already holds its true distance,
It holds initially ( and ). The maintenance
step is the crux of the argument: suppose finalizing were the first move to
break the invariant. Then either is set too low or too high.
- Too low () is impossible, because relaxation never drops an estimate below the true distance — every value of is the cost of some real walk.
- Too high (): let be a genuine shortest path from to . Walking it from (which is in ), let be the first edge that leaves — so , . By the invariant , and was relaxed when was finalized, so . Now sits on a shortest path to , and because every weight is , the rest of only adds cost: . Hence , so would have returned , not — contradiction.
The non-negativity is doing all the work in that last inequality: it guarantees extending a path never decreases its cost, so the closest frontier vertex can be safely frozen. A single negative edge breaks , so the greedy commitment becomes unsound.
On a small non-negative digraph the finalization order is exactly nondecreasing in distance, which is the visible signature of the greedy invariant. Notice that is finalized at distance via the two-hop route , beating the direct edge of weight — the relaxation through fired before was ever extracted:
Running time. Like Prim, Dijkstra does and up to operations. A binary heap gives ; a Fibonacci heap improves this to .
Bellman-Ford as a dynamic program
Dijkstra breaks when edges can be negative — its greedy finalization assumes
a finalized estimate can never improve, which negative edges violate.
trades speed for generality. It is best derived
not as relax everything repeatedly
but as a genuine
dynamic program. That framing is
worth keeping, because it explains where the comes from.
The subproblem. Bound the number of edges a walk may use. For each vertex and each budget , define
The full answer we want is for , since (as we prove below) shortest paths never need more than edges.
The recurrence. A cheapest walk to using at most edges either uses at most edges already, or it takes one final edge after a cheapest -edge walk to . Minimizing over both:
with base cases and for . This is exactly relaxation: filling row of the table from row is one full pass that relaxes every edge. The tabular form makes the layering explicit, with row holding the best walk of length :
- 1; for
- 2for to doone pass over all edges per row
- 3foreach vertex do
- 4inherit: no extra edge
- 5foreach edge do
- 6if then
- 7
- 8
- 9return and
Why stop after rounds? This is the theorem that licenses the whole algorithm. If has no negative-cost cycle, then for all the distance is achieved by a path of length . The proof is a short-circuiting argument: any walk of length visits some vertex twice, so it contains a cycle. Excising that cycle yields a shorter walk of cost no greater than the original (the removed cycle has non-negative cost). Repeating until no cycle remains leaves a simple path, with at most edges, that is no more expensive. So the rows of the DP stop changing by row .
Saving space. The recurrence only ever reads row , so we collapse the table to a single 1-D array updated in place, recovering the familiar form of Bellman-Ford as rounds of relaxing every edge. Note negative edges are fine; only a negative cycle breaks the theorem.
- 1foreach vertex do
- 2
- 3
- 4
- 5for to doV-1 full-relaxation rounds
- 6foreach edge do
- 7call
- 8foreach edge doextra pass: detect neg cycle
- 9if then
- 10return falseneg cycle reachable from s
- 11return true
Detecting negative cycles. After rounds, if any edge can still be
relaxed, some walk was improved using edges — only possible if a negative
cycle lets the cost descend without bound. So one extra pass is exactly the
test: relax everything once more, and any successful relaxation certifies a
negative cycle reachable from 2 (where cheapest cost to reach a vertex
is no
longer even well-defined). Dijkstra entirely lacks that capability. The
cost is : passes, each relaxing all
edges.
Laid out as the DP table , the layering is plain: row holds the cheapest walk of at most edges, each row computed from the one above by a single full pass of relaxation. The entries that improved on each row are shaded; the table stops changing after row , well within the bound:
Floyd-Warshall: all pairs at once
Sometimes we want for every pair of vertices, a full distance matrix. Running Bellman-Ford from each source costs , but does better with a slick dynamic program over intermediate vertices.
Number the vertices . Let be the weight of the shortest path from to whose intermediate vertices all lie in . Either the shortest such path avoids vertex (then it is ) or it routes through , splitting into an piece and a piece, each using only earlier intermediates:
One round of the recurrence makes the mechanism concrete. Starting from — direct edges only — and admitting intermediates from produces : the entry drops from to via , and the once-unreachable become finite by routing through vertex then . The five matrices below trace one round per permitted intermediate; the entries that improved in each round are shaded:
Three nested loops over , , evaluate this recurrence directly: the outer loop admits one intermediate vertex per round (exactly the five matrices above), and the inner two relax every pair against a route through it.
- 1: edge weight; on the diagonal, else
- 2for to doadmit vertex as an intermediate
- 3for to do
- 4for to do
- 5if thenrouting through is shorter
- 6
- 7return
This is time and space: compact, cache-friendly, and a clean win on dense graphs.3 It handles negative edges, and a negative entry on the diagonal () flags a negative cycle.
Choosing an algorithm
| Algorithm | Solves | Negative edges? | Time |
|---|---|---|---|
| BFS | SSSP, unweighted | n/a | |
| DAG relaxation | SSSP on a DAG | yes | |
| SSSP | no | ||
| SSSP, + cycle detection | yes | ||
| all-pairs | yes |
The decision tree is simple: unweighted, use BFS; a DAG, relax in topological order; non-negative weights, use Dijkstra (the fastest for one source); negative edges possible, use Bellman-Ford and let it police negative cycles; every pair at once, use Floyd-Warshall. All five are, at heart, disciplined applications of the same primitive.
Takeaways
- Shortest paths rest on relaxation plus two structural facts: the triangle inequality and optimal substructure.
- greedily finalizes the closest frontier vertex; correct only for non-negative weights, in with a Fibonacci heap.
- is a dynamic program: = cheapest walk of edges, and one DP row = one pass of relaxing all edges. Shortest paths need edges (short-circuit any cycle), so rounds suffice; one extra round detects negative cycles. Slower at but handles negative edges.
- On a DAG, relaxing in topological order solves SSSP in .
- computes all-pairs shortest paths in via a dynamic program over intermediate vertices.
Footnotes
- CLRS, Ch. 24 & 25 — Single-Source and All-Pairs Shortest Paths — relaxation, the triangle inequality, and optimal substructure. ↩
- Erickson, Ch. 8 & 9 — Shortest Paths — Bellman-Ford's extra relaxation pass detecting a negative cycle. ↩
- Skiena, §6 — Weighted Graph Algorithms — Floyd-Warshall's all-pairs dynamic program. ↩
╌╌ END ╌╌