Dijkstra's Algorithm
Find the shortest path from one source to every other vertex in a weighted graph with non-negative edges — by always settling the closest unsettled vertex. O((V + E) log V) with a binary heap.
The problem — BFS isn't enough anymore
BFS finds shortest paths when every edge counts as one step. But what if edges have weights — road lengths, flight costs, latencies? Now the path with the fewest edges is not necessarily the cheapest one: a direct 2-edge route of total weight 20 loses to a 3-edge route of total weight 12.
Dijkstra's algorithm solves the single-source shortest path problem on weighted graphs with non-negative edge weights: given a source s, it computes dist[v] — the minimum total weight of any path from s to v — for every vertex v at once.
The idea is a beautiful upgrade of BFS:
BFS uses a queue and processes vertices in order of number of edges from the source. Dijkstra uses a min-priority queue and processes vertices in order of total distance from the source.
That one change — replace the FIFO queue with a min-heap — turns BFS into Dijkstra.
How it works — settle the closest vertex, then relax
Every vertex is in one of three states:
- Unreached — no path found yet;
dist = ∞. - Fringe — at least one path is known;
distholds the tentative (best-so-far) distance, and the vertex sits in the min-heap. - Settled —
distis final; no shorter path can ever be found.
The main loop repeats two moves until the heap is empty:
- Settle. Pop the fringe vertex
uwith the smallest tentative distance. That distance is now final (we'll see why below). - Relax. For every edge
u → vof weightw: ifdist[u] + w < dist[v], we just found a shorter way to reachv— updatedist[v] = dist[u] + w, rememberparent[v] = u, and push(dist[v], v)into the heap.
The greedy intuition: when u is the closest unsettled vertex, any other route to u would have to pass through some other unsettled vertex first — which is already farther away, and edges can only add non-negative weight on top of that. So no detour can beat dist[u]. This is exactly the same cut-style argument as Prim's MST — in fact the two algorithms are near-twins; only the quantity in the heap differs (total path length vs. single edge weight).
Step-by-step visualization
Run Dijkstra from vertex 1 on the same weighted graph as the MST chapters — but notice we now minimize path length, not tree weight. Step forward and backward with the ← / → arrow keys or the buttons below.
What to watch:
- Each vertex's
d=label is its tentative distance — it only ever decreases, and freezes when the vertex turns green (settled). - The min-heap panel always pops the smallest entry — that's the greedy choice. (If a fringe vertex's distance improved while it sat in the heap, its old entry would go stale and be skipped when popped — lazy deletion.)
- Green edges form the growing shortest-path tree: follow
parentpointers backwards from any vertex to read off its actual shortest path. - Compare with Prim on the same graph: Dijkstra settles
5at distance 11 via4, because5 + 6 = 11beats the direct1→2→5cost of7 + 9 = 16.
Tip: step through with the ← / → arrow keys.
Why edge weights must be non-negative
Dijkstra's whole correctness rests on one assumption: once a vertex is settled, no shorter path to it can appear later. A negative edge breaks that promise.
Tiny counterexample with three vertices:
Dijkstra settles a first at dist = 2 (it's the closest). But the path s → b → a costs 3 + (−2) = 1 < 2 — too late: a is already settled and will never be re-examined. Wrong answer.
Rules of thumb:
- All weights ≥ 0 → Dijkstra (this chapter).
- Some weights < 0, no negative cycles → Bellman–Ford (next topic).
- All weights = 1 → plain BFS is simpler and faster.
Pseudocode
The standard lazy-deletion version — instead of a true decrease-key operation, just push a fresh (dist, vertex) entry and skip stale ones when popped (the if d > dist[u]: continue line). This is exactly what the visualization runs, and it is the version to memorize: it needs nothing more than the standard library's priority queue.
1DIJKSTRA(G, s):2 for each vertex v in G:3 dist[v] = ∞4 parent[v] = NIL5 dist[s] = 067 PQ = min-heap of (distance, vertex); insert (0, s)89 while PQ is not empty:10 (d, u) = extract-min(PQ)11 if d > dist[u]: continue // stale entry — skip12 // u is now SETTLED: dist[u] is final1314 for each edge (u, v) with weight w:15 if dist[u] + w < dist[v]: // found a shorter path to v16 dist[v] = dist[u] + w17 parent[v] = u18 insert (dist[v], v) into PQ1920 return dist, parent2122// Reconstruct the shortest path s -> t (after running DIJKSTRA):23PATH(s, t, parent):24 if dist[t] == ∞: return "unreachable"25 P = empty list26 for (cur = t; cur != NIL; cur = parent[cur]):27 prepend cur to P28 return P
Complexity analysis
Each vertex is settled exactly once. Each edge u → v is relaxed once (when u settles), and each successful relaxation pushes one heap entry — so the heap holds at most O(E) entries over the whole run.
Epushes and up toEpops, eachO(log E) = O(log V)(sinceE ≤ V²,log E ≤ 2 log V).- Total: O((V + E) log V) with a binary heap.
- Space: O(V + E) for the adjacency list,
dist/parentarrays, and heap.
With a Fibonacci heap the bound improves to O(E + V log V), but constants make it rare in practice. For dense graphs (E ≈ V²) a heap-free O(V²) version — scan all vertices for the minimum each round — is actually faster.
Complexity Analysis
Time Complexity
O((V + E) log V) with a binary heap; O(V²) with a flat array (better for dense graphs); O(E + V log V) with a Fibonacci heap.
Space Complexity
O(V + E) for the adjacency list, dist/parent arrays, and the heap.
Requires non-negative edge weights — a single negative edge can make a settled vertex wrong. Use Bellman–Ford when negative weights are possible, and plain BFS when all weights are equal.