Shortest Path Algorithms
The cheapest route from A to B, found without trying every route: settle nodes in order of distance and never revisit a settled one.
Why Does This Exist?
Routing, scheduling, dependency resolution, and alignment all reduce to cheapest paths on weighted graphs: fewest hops, lowest latency, smallest edit cost. Trying every route explodes combinatorially (paths multiply with branches), yet the answer has structure: any subpath of a shortest path is itself shortest, so distances can be built outward rather than enumerated. The graph encoding is in graph fundamentals; the cost language is complexity analysis.
The one idea you need first
Relaxation: if reaching costs and edge costs , then is reachable for , so . Every algorithm below repeats relaxation in a clever order until no distance improves.
Think of It Like This
Ripples from a dropped stone
Drop a stone in a pond with an uneven bottom: the ripple reaches nearby shallows first, then spreads outward, always expanding the closest unreached point next. Once the water at some spot starts moving, no later ripple can arrive sooner: the first arrival is the fastest possible, because every other route goes through water the ripple reached even later.
Dijkstra is that ripple on a graph with non-negative depths. Negative edges are whirlpools that pull the ripple backward in time, breaking the first-arrival guarantee, which is why they need a different algorithm that re-checks everything.
Where the analogy stops: real implementations use priority queues for the expansion order and edge lists for sparse graphs, not water.
How It Actually Works
BFS (unweighted): layers outward from the source; first visit is shortest in hops. Cost linear in nodes plus edges.
Dijkstra (weights ): keep tentative distances, repeatedly settle the unsettled node with the smallest tentative distance, and relax its edges. Settling order is final because any alternative route goes through an unsettled node at least as far away. Cost with a heap.
Bellman-Ford (any weights, no negative cycles reachable): relax every edge times; one more improving pass proves a negative cycle. Cost , slower but general.
All-pairs and heaps of paths. Floyd-Warshall cubes for dense small graphs; repeated Dijkstra (or Johnson with reweighting) for sparse ones. -shortest and A-star add heuristics when one target matters.
Worked example
Nodes A, B, C, D with A to B , A to C , B to C , B to D , C to D . Source A.
Start . Settle A (): relax B to , C to . Settle B (, smallest unsettled): relax C to , D to . Settle C (): relax D to . Settle D ().
Distances with parent pointers A, A, B, C: the cheapest A to D path is A to B to C to D costing , beating direct-ish A to B to D at and A to C to D at .
Watch Out For
Feeding negative edges to Dijkstra
One negative edge breaks the settling guarantee: a settled node's distance can still improve through the negative edge later, but Dijkstra never revisits it. The reported distances are then wrong with no warning, and the error propagates to every path built on them.
Symptom: distances that improve when Bellman-Ford rechecks, or paths cheaper than Dijkstra's answer found by inspection. Fix: use Bellman-Ford (or SPFA/Johnson) whenever negativity is possible, and assert non-negativity at input boundaries rather than assuming it.
Overflow and float fog on long paths
Summing thousands of weights in int32 wraps silently; comparing float path costs with == splits hairs that rounding decided. A graph with 100,000 unit edges fits the distance but not the counter that assumed small numbers.
Symptom: negative distances on positive graphs (overflow), or nondeterministic tie-breaking between equal paths. Fix: use 64-bit integers for integral weights, infinities as proper sentinels with overflow-safe addition, and epsilon-tolerant comparison for floats.
The Quick Version
- Relaxation is the shared engine; order differs.
- BFS suits unweighted graphs, Dijkstra suits non-negative weights, Bellman-Ford handles negativity and detects bad cycles.
- On the worked graph, settling order A, B, C, D gives distances 0, 1, 3, 6.
- Settled Dijkstra distances are final: never revisit, never improve.
- Negative edges need Bellman-Ford; long paths need 64-bit sums and careful comparison.