Dijkstra's Algorithm
Imagine a mapping service that must find the fastest route from your house to any address in a city. Every road segment has a nonnegative travel time. A brute-force search over all paths is hopeless. Even a modest street grid already has astronomically many routes. Yet your phone answers in milliseconds. The reason is a single-source shortest-path algorithm devised in 1956 by Edsger Dijkstra. It is one of the most-used pieces of applied computer science in existence. Dijkstra's algorithm computes the shortest distance from one starting vertex to every other vertex. It works on a weighted graph, provided no edge weight is negative. It does this greedily: it repeatedly commits to the closest still-unresolved vertex. A priority queue always identifies which vertex that is. This greedy commitment is not a heuristic. It is provably optimal. The proof is a clean example of an exchange/invariant argument. This kind of argument recurs throughout greedy algorithm design. The algorithm sits at the center of network routing protocols, GPS navigation, robotics motion planning, and dependency resolution in build systems. Anywhere costs are nonnegative and additive along a path, Dijkstra's algorithm (or a close variant) is likely running underneath. It builds directly on Edge Relaxation and Optimal Substructure. It generalizes the relaxation idea into a full graph-wide algorithm with a rigorous stopping guarantee. This article gives the precise algorithm and a full pseudocode implementation using a priority queue. It includes a diagrammatic trace, the heap-based complexity analysis, and the correctness proof. That proof is based on a cut/invariant argument. It also previews how the single-source idea extends to computing distances between all pairs of vertices. This is the subject of the All-Pairs Shortest Paths Overview.
- Dijkstra's algorithm finds shortest paths from one source to all vertices, assuming nonnegative edge weights.
- It greedily extracts the closest unvisited vertex from a priority queue and relaxes its outgoing edges.
- Correctness rests on an invariant: once a vertex is finalized, its shortest-path distance is correct forever.
- With a binary heap, runtime is O((V + E) log V); with a Fibonacci heap, O(E + V log V).
- Negative edge weights break the greedy invariant and require Bellman-Ford Algorithm instead.
- It is the computational core of internet routing (OSPF), GPS navigation, and many graph-search applications.
Once you greedily finalize the closest unvisited vertex by true shortest distance, that distance can never later be improved — so committing to it is always safe.
The idea
Dijkstra's algorithm solves the single-source shortest path (SSSP) problem on a weighted, directed (or undirected) graph with a nonnegative weight function , and a designated source vertex . For every vertex , the goal is to compute , the weight of the shortest path from to (or if is unreachable). The algorithm maintains, for every vertex , a tentative distance estimate , initialized to (except ), and a predecessor pointer used to reconstruct the actual path. As in basic Edge Relaxation and Optimal Substructure, the core primitive is the relax operation on an edge with weight : if , we set and . What Dijkstra's algorithm adds is a disciplined order in which to perform relaxations, guaranteeing that each vertex is relaxed from correctly-computed neighbors and processed exactly once. The algorithm partitions vertices into a finalized set (whose shortest distances are known to be exact) and the rest. It repeatedly selects the vertex with the smallest tentative distance , adds it to , and relaxes all edges leaving . Because only grows and each vertex enters with its true shortest distance already locked in, the process resembles a wavefront expanding outward from in order of increasing distance — closest vertices are settled first.
How it works
Step-by-step mechanism: 1. Initialize and for all other ; set for all . 2. Insert all vertices into a min-priority queue keyed by . 3. While the priority queue is nonempty: a. Extract the vertex with minimum (this is the "finalize" step). b. For each edge leaving , relax it: if , update and , and update 's priority in the queue (a decrease-key operation). 4. When the queue is empty, every reachable vertex has its correct shortest distance in , and the shortest-path tree is encoded by . A key implementation detail: many practical implementations use a lazy priority queue that allows duplicate entries rather than true decrease-key, checking a `visited`/`finalized` flag to skip stale entries. This trades a slightly larger queue for simpler code, without changing the asymptotic bound when using a binary heap. ``` function DIJKSTRA(Graph G, weight w, source s): for each vertex v in G.V: d[v] = INFINITY pi[v] = NIL finalized[v] = false d[s] = 0 Q = new MinPriorityQueue() // keyed by d[v] Q.insert(s, priority = 0) while Q is not empty: u = Q.extract_min() // vertex with smallest d[u] if finalized[u]: continue // stale entry (lazy deletion) finalized[u] = true for each edge (u, v) with weight w(u, v) in G.adj[u]: if finalized[v]: continue newDist = d[u] + w(u, v) if newDist < d[v]: d[v] = newDist pi[v] = u Q.insert(v, priority = newDist) // "lazy" decrease-key return d, pi ``` To reconstruct the actual shortest path to a vertex , follow back to and reverse the sequence.
Complexity & correctness
Time complexity. The algorithm performs exactly extract-min operations (each vertex is finalized once) and up to decrease-key/insert operations (once per edge relaxation that improves a distance). With a binary heap, extract-min costs and each insert/decrease-key costs , giving
With a Fibonacci heap, extract-min is amortized but decrease-key is amortized, giving the asymptotically better
which matters for dense graphs (), where it beats the binary-heap bound. In practice, binary heaps (or even a simple array-based implementation for dense graphs) are often faster in wall-clock time due to lower constant factors and better cache behavior, illustrating a classic asymptotics-versus-constants tradeoff.
Space complexity is : adjacency-list storage plus arrays for , , and the priority queue's contents.
Correctness proof (invariant argument). The central claim is: > Invariant. At the moment a vertex is extracted from the priority queue (finalized), , the true shortest-path distance. Proof by induction on the order of extraction. Let be the order in which vertices are finalized, with (this ordering holds by definition of extract-min). Base case: , and , correct since all weights are nonnegative (no negative cycle can make a shorter path). Inductive step: Suppose for all . Consider , about to be extracted. Suppose for contradiction that . Take the true shortest path from to . Since is finalized and is not yet finalized, must cross from the finalized set to the unfinalized set at some edge , where is already finalized (so by the inductive hypothesis) and is not yet finalized. Because all edge weights are nonnegative, the prefix of up to is itself a shortest path, so
But was finalized before , so at the time was processed the edge was relaxed, giving . Then would have a smaller tentative distance than and would have been extracted before or at the same time as — contradicting the assumption that was chosen as the next minimum. Hence no such crossing point can give a strictly shorter path, and . The nonnegativity of weights is essential here: it is what guarantees the prefix of a shortest path is itself shortest, and what prevents a later relaxation through an unfinalized vertex from ever undercutting an already-finalized distance. This is precisely why the algorithm fails on graphs with negative edges — the invariant's crossing-edge argument collapses, since a longer detour through a very negative edge could beat a currently "shortest" finalized value.
Tradeoffs. Dijkstra's algorithm assumes weights are known upfront and nonnegative; it does not handle negative edges (use Bellman-Ford Algorithm) and is not directly parallelizable in its classic sequential form due to the strict extraction order, though bucket-based and parallel variants (e.g., Δ-stepping) relax this at the cost of extra bookkeeping.
Worked example
Consider the graph from the diagram: vertices , source , edges , , , , , , .
| Step | Extracted (finalized) | before relax | Edges relaxed | Updated values |
|---|---|---|---|---|
| 0 | — | — | initial | |
| 1 | (0) | as above | : ; : | |
| 2 | (1) | : | ||
| 3 | (3) | : | ||
| 4 | (4) | : ; : (skip) | ||
| 5 | (6) | has no outgoing edges to relax here (already minimal) | — | |
| 6 | (7) | — | : (skip) | — |
Final distances: . Notice at step 5, is extracted with even though (with ) has not yet been finalized — this is exactly the invariant in action: since , and all weights are nonnegative, no path through the still-open could possibly produce a shorter route to , so 's distance is safely locked at 6. Reconstructing the path to : , , , giving path with total weight , matching .
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Using negative edge weights | The core invariant's proof relies on nonnegativity; negative edges let a later path undercut a "finalized" distance | Use Bellman-Ford Algorithm for graphs with negative (but no negative-cycle) weights |
| Forgetting to skip stale/finalized entries in a lazy heap | Duplicate queue entries for the same vertex cause redundant or even incorrect re-relaxation if not filtered | Check a `finalized[]` flag immediately after extract-min and `continue` if already set |
| Treating unreachable vertices as an error instead of | Some implementations crash on `d[v] == INFINITY` during path reconstruction | Explicitly handle and report "unreachable" |
| Assuming it finds shortest paths by number of edges | Confusing weighted shortest path with BFS's unweighted shortest path | Use BFS when all edges have equal weight 1; use Dijkstra only for weighted nonnegative graphs |
| Re-inserting into the queue instead of true decrease-key without accounting for extra entries in complexity analysis | Miscounting queue size, leading to wrong big-O reasoning | Recognize the lazy approach still gives since |
Pioneers & history
Origins · 1 minEdsger W. Dijkstra and how this was discovered
Dijkstra devised his shortest-path algorithm in 1956 as a twenty-minute mental exercise while sitting in a café in Amsterdam with his fiancée, designing it without paper to demonstrate the capabilities of the new ARMAC computer. He later became one of the founders of structured programming and formal program verification, famous for his terse, principled writing style and his insistence that programs be understood mathematically rather than merely tested into correctness.
Where this matters
Internet routing protocols such as OSPF (Open Shortest Path First) run a Dijkstra computation on each router's local view of the network topology to determine the next hop for every destination. GPS and mapping systems (road networks with travel-time weights) use Dijkstra's algorithm or heavily optimized descendants like contraction hierarchies and A search, which augment Dijkstra with a heuristic to prune the search space while preserving the same correctness guarantee. Robotics and game AI use it (or its generalization A) for pathfinding on grids and navigation meshes. Dependency and build systems occasionally reduce ordering problems to shortest-path computations when costs represent resource usage. It also serves as the direct algorithmic ancestor discussed when introducing the All-Pairs Shortest Paths Overview, since running Dijkstra's algorithm from every vertex is one standard way to solve the all-pairs problem on graphs with nonnegative weights.
Ask the tutor: How does the choice of priority queue implementation change Dijkstra's algorithm's performance on very dense versus very sparse graphs?
Why doesn't Dijkstra's algorithm work with negative edge weights?
The correctness proof relies on showing that once a vertex is extracted with the smallest tentative distance, no path through a not-yet-finalized vertex can beat it. This relies on the fact that extending any path with more nonnegative edges can only keep its length the same or increase it. If an edge can be negative, a longer, currently-unexplored path could still end up shorter overall, meaning a vertex "finalized" early might later be undercut. The algorithm has no mechanism to revisit finalized vertices, so it would produce an incorrect, too-large distance without any warning. Bellman-Ford handles this by allowing repeated relaxation passes.
How is Dijkstra's algorithm related to breadth-first search?
Breadth-first search is the special case of shortest-path computation where every edge has weight 1. In that setting, Dijkstra's algorithm using a plain FIFO queue (rather than a priority queue) reduces exactly to BFS, since the vertices at distance are always extracted before those at distance . Conceptually, both algorithms expand an ever-growing "frontier" of finalized vertices outward from the source; Dijkstra's algorithm generalizes this frontier expansion to handle varying, nonnegative edge costs by using a priority queue instead of a simple queue.
What is the difference between Dijkstra's algorithm and A* search?
A search is Dijkstra's algorithm augmented with a heuristic function that estimates the remaining distance from to the goal, prioritizing vertices by instead of just . If is admissible (never overestimates the true remaining distance), A still returns the optimal path but typically explores far fewer vertices, because the heuristic steers the search toward the goal. Setting for all recovers plain Dijkstra's algorithm exactly, showing A* is a strict generalization.
Can Dijkstra's algorithm be used to find the shortest path between just two specific vertices, not all pairs?
Yes — for a single source-destination query, you can stop the algorithm early as soon as the destination vertex is extracted from the priority queue (finalized), since at that point its distance is guaranteed correct by the invariant. This early termination does not change the worst-case asymptotic complexity but often saves significant work in practice, especially in large sparse graphs where the destination is relatively close to the source.
Why use a priority queue instead of just scanning all vertices for the minimum each time?
Scanning all unfinalized vertices for the minimum value takes time per iteration, giving an overall algorithm — actually a reasonable choice for dense graphs. A priority queue (binary heap) reduces the extract-min cost to , which is a major win for sparse graphs where , yielding instead of . The right choice depends on graph density: array-based Dijkstra can outperform heap-based Dijkstra on dense graphs due to lower overhead.
Does Dijkstra's algorithm work on directed graphs, undirected graphs, or both?
Both. On an undirected graph, each undirected edge with weight is simply treated as two directed edges and , both with weight , in the adjacency representation. The algorithm's logic (relaxation, finalization, invariant) is identical regardless of directionality; what matters is only that the weight function assigns a single nonnegative cost to traversing each edge in whichever direction is used.
How does Dijkstra's algorithm relate to computing shortest paths between every pair of vertices?
Running Dijkstra's algorithm once from every vertex as a source gives all-pairs shortest paths in total time (using a binary heap), which is efficient for sparse graphs with nonnegative weights. This is one of the standard approaches compared against dynamic-programming methods like Floyd-Warshall, which handles negative edges (without negative cycles) directly in regardless of density — a tradeoff explored fully in the All-Pairs Shortest Paths Overview.