A Bit of History
In 1956, Dutch computer scientist Edsger W. Dijkstra was thinking about the shortest route between two cities in the Netherlands — partly to demonstrate the capabilities of a new computer to a general audience. By his own later account, he designed the algorithm in about twenty minutes, without pencil or paper, while having coffee with his fiancée at a café terrace in Amsterdam. He didn't publish it until 1959, in a short three-page paper that also introduced an algorithm for minimum spanning trees (closely related to Kruskal's algorithm and Prim's algorithm, covered in an upcoming deep dive). Dijkstra later reflected that the algorithm's elegance came from deliberately avoiding pencil and paper — forcing him to keep the method simple enough to hold entirely in his head.
Working Principle
Dijkstra's algorithm solves the single-source shortest path problem on a weighted graph with non-negative edge weights. It generalizes BFS's "process closest-first" idea: instead of a plain queue (which assumes every edge costs "1"), it uses a min-priority queue keyed by tentative distance, always expanding the currently-closest unfinalized vertex next.
Maintain a tentative distance \(d[v]\) for every vertex (initialized to \(\infty\), except \(d[\text{source}]=0\)). Repeatedly extract the vertex \(u\) with the smallest tentative distance that hasn't been finalized yet, mark it finalized, and relax every outgoing edge \((u,v)\): if \(d[u] + w(u,v) < d[v]\), update \(d[v]\) to that smaller value.
$$\textbf{Relaxation: } d[v] \leftarrow \min\big(d[v],\ d[u] + w(u,v)\big)$$
Analogy: Filling a Reservoir from the Cheapest Sources First
Think of \(d[v]\) as "the cheapest known price of water reaching town \(v\)." Every time you finalize the cheapest-so-far town, you check whether routing water through it makes any neighboring town's price cheaper than what you'd previously found. Because you always finalize the globally cheapest remaining option first, once a town is finalized, no future (necessarily more expensive) route could ever beat its price — which is exactly why the greedy choice is safe.
Worked Example
Consider a small weighted graph: \(A \to B\) (weight 4), \(A \to C\) (weight 1), \(C \to B\) (weight 1), \(B \to D\) (weight 1), \(C \to D\) (weight 5).
flowchart LR
A -->|4| B
A -->|1| C
C -->|1| B
B -->|1| D
C -->|5| D
| Step | Finalized | d[A] | d[B] | d[C] | d[D] |
|---|---|---|---|---|---|
| Init | — | 0 | ∞ | ∞ | ∞ |
| 1 | A | 0 | 4 | 1 | ∞ |
| 2 | C (dist 1) | 0 | 2 (via C) | 1 | 6 (via C) |
| 3 | B (dist 2) | 0 | 2 | 1 | 3 (via B) |
| 4 | D (dist 3) | 0 | 2 | 1 | 3 |
Notice how \(d[B]\) drops from 4 to 2 once \(C\) is finalized and its outgoing edge \(C \to B\) is relaxed — the direct edge \(A \to B\) (weight 4) was never actually the shortest route; going through \(C\) (weight \(1+1=2\)) is cheaper. This is exactly why Dijkstra's algorithm must consider all edges out of a vertex before that vertex's neighbors can be trusted as final.
Why Greedy Works Here
This is the direct-proof and induction machinery from Part 1 in action. Claim: when a vertex \(u\) is extracted from the priority queue (finalized), \(d[u]\) already equals the true shortest-path distance from the source.
Proof by induction on the order vertices are finalized. Base case: the source is finalized first with \(d[\text{source}]=0\), trivially correct. Inductive step: suppose every previously finalized vertex has a correct final distance. Let \(u\) be the next vertex extracted, with the smallest tentative distance among all unfinalized vertices. Any path to \(u\) must, at some point, leave the "finalized" set for the last time — crossing some edge \((x, y)\) where \(x\) is finalized and \(y\) is not. Since edge weights are non-negative, the length of that path is at least \(d[x] + w(x,y) \geq d[y] \geq d[u]\) (the last inequality holds because \(u\) was chosen to have the smallest tentative distance). So no unfinalized route can beat \(d[u]\) — it is already optimal.
The Proof Breaks with Negative Weights
The inequality \(d[x] + w(x,y) \geq d[y]\) is where non-negativity is used — if \(w(x,y)\) could be negative, a later, cheaper route through a currently-unfinalized vertex could beat an already-finalized vertex's distance, silently breaking correctness (with no error or warning — Dijkstra's algorithm will simply return a wrong, too-large answer). This is precisely the gap that the Bellman-Ford algorithm (an upcoming deep dive) is designed to fill.
Complexity Analysis
With a binary-heap priority queue, each of the \(V\) extract-min operations costs \(O(\log V)\), and each of the \(E\) relaxations may trigger a \(O(\log V)\) decrease-key (or, in the common simplified implementation, a fresh \(O(\log V)\) insertion, discarding stale entries lazily):
$$\text{Time: } O((V+E)\log V) \qquad \text{Space: } O(V)$$
A naive \(O(V^2)\) array-based implementation (scan all unfinalized vertices for the minimum, every time) is actually faster on very dense graphs where \(E\) approaches \(V^2\) — a good reminder that asymptotic complexity depends on the graph's shape, not just its algorithm.
Implementation
Real-World Applications
GPS Navigation and Network Routing
Every "fastest route" calculation in a mapping app is, at its core, a shortest-path problem on a weighted graph where intersections are vertices and road segments are edges weighted by estimated travel time. Production systems use highly optimized variants (contraction hierarchies, bidirectional search, A* with geographic heuristics — previewed in an upcoming deep dive), but the correctness guarantee they all build on is exactly Dijkstra's greedy relaxation argument. The same algorithm (or its variants) also computes optimal routing tables inside internet routers (OSPF, a link-state routing protocol, literally runs a Dijkstra computation on the network topology).
Exercises
- Run Dijkstra's algorithm by hand on the graph in the worked example, but starting from vertex \(C\) instead of \(A\).
- Construct a small graph with a negative edge weight where Dijkstra's algorithm produces an incorrect shortest-path distance, and explain exactly which step of the correctness proof fails.
- Modify the Python implementation above to also track and reconstruct the shortest path (not just its length) using a parent map, matching the technique from the BFS deep dive.
- Challenge: Explain why running Dijkstra's algorithm from a single source and stopping early the moment your specific target vertex is finalized is still correct and often faster in practice, even though it doesn't compute distances to every vertex.
Limitations
No Negative Weights, No Free Lunch on Density
Dijkstra's algorithm requires non-negative edge weights (see the correctness proof above) — use the Bellman-Ford algorithm when negative weights are possible. And while \(O((V+E)\log V)\) is excellent for sparse graphs, on very dense graphs the constant-factor overhead of heap operations can make a simpler \(O(V^2)\) array scan faster in practice — always profile before assuming the asymptotically better algorithm wins on your actual data.