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.

Key takeaways
  • 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.
The core insight

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 G=(V,E)G = (V, E) with a nonnegative weight function w:E→R≥0w: E \to \mathbb{R}_{\geq 0}, and a designated source vertex ss. For every vertex vv, the goal is to compute δ(s,v)\delta(s, v), the weight of the shortest path from ss to vv (or ∞\infty if vv is unreachable). The algorithm maintains, for every vertex vv, a tentative distance estimate d[v]d[v], initialized to ∞\infty (except d[s]=0d[s] = 0), and a predecessor pointer π[v]\pi[v] used to reconstruct the actual path. As in basic Edge Relaxation and Optimal Substructure, the core primitive is the relax operation on an edge (u,v)(u,v) with weight w(u,v)w(u,v): if d[u]+w(u,v)<d[v]d[u] + w(u,v) < d[v], we set d[v]←d[u]+w(u,v)d[v] \leftarrow d[u] + w(u,v) and π[v]←u\pi[v] \leftarrow u. 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 SS (whose shortest distances are known to be exact) and the rest. It repeatedly selects the vertex u∉Su \notin S with the smallest tentative distance d[u]d[u], adds it to SS, and relaxes all edges leaving uu. Because SS only grows and each vertex enters SS with its true shortest distance already locked in, the process resembles a wavefront expanding outward from ss in order of increasing distance — closest vertices are settled first.

How it works

Step-by-step mechanism: 1. Initialize d[s]=0d[s] = 0 and d[v]=∞d[v] = \infty for all other vv; set π[v]=nil\pi[v] = \text{nil} for all vv. 2. Insert all vertices into a min-priority queue keyed by d[⋅]d[\cdot]. 3. While the priority queue is nonempty: a. Extract the vertex uu with minimum d[u]d[u] (this is the "finalize" step). b. For each edge (u,v)(u, v) leaving uu, relax it: if d[u]+w(u,v)<d[v]d[u] + w(u,v) < d[v], update d[v]d[v] and π[v]\pi[v], and update vv's priority in the queue (a decrease-key operation). 4. When the queue is empty, every reachable vertex has its correct shortest distance in d[⋅]d[\cdot], and the shortest-path tree is encoded by π[⋅]\pi[\cdot]. 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 tt, follow π[t],π[π[t]],…\pi[t], \pi[\pi[t]], \dots back to ss and reverse the sequence.

Figure
Dijkstra's algorithm expanding a shortest-path frontier from source A
4 1 3 7 2 6 3 A:0 B:4 C:1 D:7 E:3 F:∞ Green = finalized Orange = in queue, tentative Gray = undiscovered

Complexity & correctness

Time complexity. The algorithm performs exactly ∣V∣|V| extract-min operations (each vertex is finalized once) and up to ∣E∣|E| decrease-key/insert operations (once per edge relaxation that improves a distance). With a binary heap, extract-min costs O(log⁡V)O(\log V) and each insert/decrease-key costs O(log⁡V)O(\log V), giving

O(Vlog⁡V+Elog⁡V)=O((V+E)log⁡V).O(V \log V + E \log V) = O((V + E)\log V).

With a Fibonacci heap, extract-min is O(log⁡V)O(\log V) amortized but decrease-key is O(1)O(1) amortized, giving the asymptotically better

O(E+Vlog⁡V),O(E + V \log V),

which matters for dense graphs (E=Θ(V2)E = \Theta(V^2)), where it beats the binary-heap bound. In practice, binary heaps (or even a simple array-based O(V2)O(V^2) 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 O(V+E)O(V + E): adjacency-list storage plus arrays for dd, π\pi, and the priority queue's contents.

Correctness proof (invariant argument). The central claim is: > Invariant. At the moment a vertex uu is extracted from the priority queue (finalized), d[u]=δ(s,u)d[u] = \delta(s, u), the true shortest-path distance. Proof by induction on the order of extraction. Let u1,u2,…u_1, u_2, \dots be the order in which vertices are finalized, with d[u1]≤d[u2]≤⋯d[u_1] \le d[u_2] \le \cdots (this ordering holds by definition of extract-min). Base case: u1=su_1 = s, and d[s]=0=δ(s,s)d[s] = 0 = \delta(s,s), correct since all weights are nonnegative (no negative cycle can make a shorter path). Inductive step: Suppose d[ui]=δ(s,ui)d[u_i] = \delta(s, u_i) for all i<ki < k. Consider uku_k, about to be extracted. Suppose for contradiction that d[uk]>δ(s,uk)d[u_k] > \delta(s, u_k). Take the true shortest path PP from ss to uku_k. Since ss is finalized and uku_k is not yet finalized, PP must cross from the finalized set to the unfinalized set at some edge (x,y)(x, y), where xx is already finalized (so d[x]=δ(s,x)d[x] = \delta(s,x) by the inductive hypothesis) and yy is not yet finalized. Because all edge weights are nonnegative, the prefix of PP up to yy is itself a shortest path, so

δ(s,y)=d[x]+w(x,y)≤δ(s,uk)<d[uk].\delta(s, y) = d[x] + w(x,y) \le \delta(s, u_k) < d[u_k].

But xx was finalized before uku_k, so at the time xx was processed the edge (x,y)(x,y) was relaxed, giving d[y]≤d[x]+w(x,y)=δ(s,y)≤δ(s,uk)<d[uk]d[y] \le d[x] + w(x,y) = \delta(s,y) \le \delta(s,u_k) < d[u_k]. Then yy would have a smaller tentative distance than uku_k and would have been extracted before or at the same time as uku_k — contradicting the assumption that uku_k was chosen as the next minimum. Hence no such crossing point can give a strictly shorter path, and d[uk]=δ(s,uk)d[u_k] = \delta(s, u_k). ■\blacksquare 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 {A,B,C,D,E,F}\{A, B, C, D, E, F\}, source AA, edges A−B:4A{-}B{:}4, A−C:1A{-}C{:}1, B−D:3B{-}D{:}3, B−E:7B{-}E{:}7, C−E:2C{-}E{:}2, D−F:6D{-}F{:}6, E−F:3E{-}F{:}3.

StepExtracted (finalized)dd before relaxEdges relaxedUpdated dd values
0—A:0, B:∞, C:∞, D:∞, E:∞, F:∞A{:}0,\,B{:}\infty,\,C{:}\infty,\,D{:}\infty,\,E{:}\infty,\,F{:}\infty—initial
1AA (0)as aboveA−BA{-}B: 0+4=4<∞0+4=4 < \infty; A−CA{-}C: 0+1=1<∞0+1=1 < \inftyB:4, C:1B{:}4,\,C{:}1
2CC (1)B:4,D:∞,E:∞,F:∞B{:}4,D{:}\infty,E{:}\infty,F{:}\inftyC−EC{-}E: 1+2=3<∞1+2=3 < \inftyE:3E{:}3
3EE (3)B:4,D:∞,F:∞B{:}4,D{:}\infty,F{:}\inftyE−FE{-}F: 3+3=6<∞3+3=6 < \inftyF:6F{:}6
4BB (4)D:∞,F:6D{:}\infty,F{:}6B−DB{-}D: 4+3=7<∞4+3=7 < \infty; B−EB{-}E: 4+7=11≮34+7=11 \not< 3 (skip)D:7D{:}7
5FF (6)D:7D{:}7FF has no outgoing edges to relax here (already minimal)—
6DD (7)—D−FD{-}F: 7+6=13≮67+6=13 \not< 6 (skip)—

Final distances: d[A]=0, d[B]=4, d[C]=1, d[D]=7, d[E]=3, d[F]=6d[A]=0,\ d[B]=4,\ d[C]=1,\ d[D]=7,\ d[E]=3,\ d[F]=6. Notice at step 5, FF is extracted with d[F]=6d[F] = 6 even though DD (with d[D]=7d[D]=7) has not yet been finalized — this is exactly the invariant in action: since 6<76 < 7, and all weights are nonnegative, no path through the still-open DD could possibly produce a shorter route to FF, so FF's distance is safely locked at 6. Reconstructing the path to FF: π[F]=E\pi[F] = E, π[E]=C\pi[E] = C, π[C]=A\pi[C] = A, giving path A→C→E→FA \to C \to E \to F with total weight 1+2+3=61+2+3=6, matching d[F]d[F].

Common pitfalls & traps

PitfallWhy it happensFix
Using negative edge weightsThe core invariant's proof relies on nonnegativity; negative edges let a later path undercut a "finalized" distanceUse Bellman-Ford Algorithm for graphs with negative (but no negative-cycle) weights
Forgetting to skip stale/finalized entries in a lazy heapDuplicate queue entries for the same vertex cause redundant or even incorrect re-relaxation if not filteredCheck a `finalized[]` flag immediately after extract-min and `continue` if already set
Treating unreachable vertices as an error instead of ∞\inftySome implementations crash on `d[v] == INFINITY` during path reconstructionExplicitly handle π[v]=NIL\pi[v] = \text{NIL} and report "unreachable"
Assuming it finds shortest paths by number of edgesConfusing weighted shortest path with BFS's unweighted shortest pathUse 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 analysisMiscounting queue size, leading to wrong big-O reasoningRecognize the lazy approach still gives O((V+E)log⁡E)=O((V+E)log⁡V)O((V+E)\log E) = O((V+E)\log V) since E=O(V2)E = O(V^2)

Pioneers & history

Origins · 1 minEdsger W. Dijkstra and how this was discovered
EW
Edsger W. Dijkstra
1930–2002 · Dutch computer scientist, Eindhoven and Austin

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.

History
1956
Dijkstra conceives the shortest-path algorithm, later published in 1959.
1959
"A Note on Two Problems in Connexion with Graphs" appears in Numerische Mathematik.
1984
Fibonacci heaps introduced by Fredman and Tarjan, improving Dijkstra's asymptotic runtime to O(E+Vlog⁡V)O(E + V\log V).
1990
s–2000s: Variants (A*, bidirectional search, contraction hierarchies) extend Dijkstra's core idea to large-scale routing.
Present: Dijkstra's algorithm remains the textbook baseline for shortest-path computation in graphs with nonnegative weights.

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 kk are always extracted before those at distance k+1k+1. 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 h(v)h(v) that estimates the remaining distance from vv to the goal, prioritizing vertices by d[v]+h(v)d[v] + h(v) instead of just d[v]d[v]. If hh 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 h(v)=0h(v) = 0 for all vv 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 d[⋅]d[\cdot] value takes O(V)O(V) time per iteration, giving an overall O(V2)O(V^2) algorithm — actually a reasonable choice for dense graphs. A priority queue (binary heap) reduces the extract-min cost to O(log⁡V)O(\log V), which is a major win for sparse graphs where E=O(V)E = O(V), yielding O((V+E)log⁡V)=O(Vlog⁡V)O((V+E)\log V) = O(V \log V) instead of O(V2)O(V^2). 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 (u,v)(u,v) with weight ww is simply treated as two directed edges u→vu \to v and v→uv \to u, both with weight ww, 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 O(V⋅(Elog⁡V))O(V \cdot (E \log V)) 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 O(V3)O(V^3) regardless of density — a tradeoff explored fully in the All-Pairs Shortest Paths Overview.

Before this

Where this leads