Dijkstra's algorithm
Dijkstra's algorithm settles vertices in order of distance, each time taking the closest unsettled one; with non-negative weights this is correct, and a priority queue makes it O((n + m) log n).
Learning goals
- Explain why non-negative weights make the greedy choice safe
- Implement Dijkstra with a priority queue and lazy deletion
- Count how many outdated entries the queue produces
- Describe an indexed heap with decrease-key
- Reconstruct a path from parent links
Prerequisites
Road routing, network latency and game movement all need the cheapest path with positive costs. Dijkstra's greedy idea, always finish the closest unfinished vertex next, solves them in near-linear time.
Nonnegative weights
Dijkstra keeps a set of settled vertices whose distances are final. At each step it settles the unsettled vertex u with the smallest estimate. Why is it final? Any other route to u must leave the settled set through some other unsettled vertex x, whose estimate is at least as large, and then add non-negative weights, so it cannot be cheaper than the estimate of u.
The argument needs every weight to be at least 0. Zero weights are fine. This simple version scans for the minimum each time and costs O(n²), which is best for dense graphs; the heap versions below win on sparse ones.
1static long[] dijkstraQuadratic(int n, int[][] edges, int s) { // O(n^2); weights >= 02long INF = Long.MAX_VALUE / 2;3long[][] w = new long[n][n];4for (long[] r : w) Arrays.fill(r, INF);5for (int[] e : edges) w[e[0]][e[1]] = Math.min(w[e[0]][e[1]], e[2]); // lightest parallel edge6long[] dist = new long[n];7Arrays.fill(dist, INF);8dist[s] = 0;9boolean[] done = new boolean[n];10for (int step = 0; step < n; step++) {11int u = -1;12for (int v = 0; v < n; v++) if (!done[v] && dist[v] < INF && (u == -1 || dist[v] < dist[u])) u = v;13if (u == -1) break; // the rest is unreachable14done[u] = true;15for (int v = 0; v < n; v++)16if (w[u][v] < INF && dist[u] + w[u][v] < dist[v]) dist[v] = dist[u] + w[u][v];17}18return dist;19}
Dijkstra's shortest paths, and why negative edges break it
Interactive · edit the inputdist[] from vertex 0
Distances start at ∞ except the source. Each step settles the closest unsettled vertex and relaxes its outgoing edges. Its distance is declared final, which is only justified when no edge is negative.
1static int[] dijkstra(int n, List<int[]>[] adj, int src) { // adj[u] = list of {v, weight}2int[] dist = new int[n]; Arrays.fill(dist, INF); dist[src] = 0;3boolean[] done = new boolean[n];4for (int step = 0; step < n; step++) {5int u = -1;6for (int v = 0; v < n; v++) if (!done[v] && dist[v] < INF && (u == -1 || dist[v] < dist[u])) u = v; // closest unsettled vertex7if (u == -1) break;8done[u] = true; // final: valid only when no edge is negative9for (int[] e : adj[u]) if (!done[e[0]] && dist[u] + e[1] < dist[e[0]]) dist[e[0]] = dist[u] + e[1]; // relax10}11return dist;12}
Live state
vertices
4
negative
false
Execution trace (dry run table)Show 11 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | vertices=4 negative=false | Distances start at ∞ except the source. Each step settles the closest unsettled vertex and relaxes its outgoing edges. Its distance is declared final, which is only justified when no edge is negative. |
| 2 | L8 | settled=0 dist=0 | Settle vertex 0 with distance 0: from now on it will not be updated. |
| 3 | L9 | edge=0>1:4 new dist=4 | Relax 0>1: 0 + 4 = 4 beats the old distance. |
| 4 | L9 | edge=0>2:1 new dist=1 | Relax 0>2: 0 + 1 = 1 beats the old distance. |
| 5 | L8 | settled=2 dist=1 | Settle vertex 2 with distance 1: from now on it will not be updated. |
| 6 | L9 | edge=2>1:2 new dist=3 | Relax 2>1: 1 + 2 = 3 beats the old distance. |
| 7 | L9 | edge=2>3:5 new dist=6 | Relax 2>3: 1 + 5 = 6 beats the old distance. |
| 8 | L8 | settled=1 dist=3 | Settle vertex 1 with distance 3: from now on it will not be updated. |
| 9 | L9 | edge=1>3:1 new dist=4 | Relax 1>3: 3 + 1 = 4 beats the old distance. |
| 10 | L8 | settled=3 dist=4 | Settle vertex 3 with distance 4: from now on it will not be updated. |
| 11 | L11 | return=[0, 3, 1, 4] reference=[0, 3, 1, 4] | Distances [0, 3, 1, 4] match the Bellman-Ford reference. |
Priority queue
A **priority queue** hands back the unsettled vertex with the smallest estimate in O(log n). Each time an edge improves an estimate, push the new {distance, vertex} pair. The total number of pushes is at most the number of edges, so the running time is O(m log m), which is O(m log n).
Java's PriorityQueue has no decrease-key operation, so the usual approach pushes a fresh entry and ignores the old one when it comes out, described next. The animation shows the queue as a sorted list of pairs.
1static long[] dijkstraHeap(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}, all w >= 02long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n];4Arrays.fill(dist, INF);5dist[s] = 0;6PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0])); // {distance, vertex}7pq.add(new long[]{0, s});8while (!pq.isEmpty()) {9long[] top = pq.poll();10int u = (int) top[1];11if (top[0] > dist[u]) continue; // outdated entry12for (int[] e : adj[u])13if (dist[u] + e[1] < dist[e[0]]) {14dist[e[0]] = dist[u] + e[1];15pq.add(new long[]{dist[e[0]], e[0]});16}17}18return dist;19}
Dijkstra with a priority queue: lazy deletion and path reconstruction
Interactive · edit the inputundirected graph · 6 vertices · 9 edges
d=0
queue {dist, vertex}, smallest first
dist[v]
Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it.
1long[] dijkstra(int n, List<int[]>[] adj, int s, int[] parent) { // adj[u] = {v, w}, all w >= 02long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n]; Arrays.fill(dist, INF); dist[s] = 0;4Arrays.fill(parent, -1);5PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0])); // {distance, vertex}6pq.add(new long[]{0, s});7while (!pq.isEmpty()) {8long[] top = pq.poll(); int u = (int) top[1];9if (top[0] > dist[u]) continue; // outdated entry: u was already improved10for (int[] e : adj[u]) {11if (dist[u] + e[1] >= dist[e[0]]) continue;12dist[e[0]] = dist[u] + e[1]; parent[e[0]] = u;13pq.add(new long[]{dist[e[0]], e[0]}); // lazy: the old entry stays in the queue14}15}16return dist;17}18List<Integer> pathTo(int[] parent, int t) { // follow parents back from t19LinkedList<Integer> p = new LinkedList<>();20for (int v = t; v != -1; v = parent[v]) p.addFirst(v);21return p;22}
Live state
source
0
Execution trace (dry run table)Show 28 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L6 | source=0 | Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it. |
| 2 | L8 | u=0 dist[u]=0 | Pop {0, 0}: the distance to 0 is final (0). Relax its edges. |
| 3 | L12 | u=0 v=1 dist[v]=7 | 0 → 1: 0 + 7 = 7. dist[1] = 7, parent[1] = 0, and a new entry {7, 1} is pushed. |
| 4 | L12 | u=0 v=2 dist[v]=9 | 0 → 2: 0 + 9 = 9. dist[2] = 9, parent[2] = 0, and a new entry {9, 2} is pushed. |
| 5 | L12 | u=0 v=5 dist[v]=14 | 0 → 5: 0 + 14 = 14. dist[5] = 14, parent[5] = 0, and a new entry {14, 5} is pushed. |
| 6 | L8 | u=1 dist[u]=7 | Pop {7, 1}: the distance to 1 is final (7). Relax its edges. |
| 7 | L10 | u=1 v=0 candidate=14 | 1 → 0: 7 + 7 = 14 does not beat 0. |
| 8 | L10 | u=1 v=2 candidate=17 | 1 → 2: 7 + 10 = 17 does not beat 9. |
| 9 | L12 | u=1 v=3 dist[v]=22 | 1 → 3: 7 + 15 = 22. dist[3] = 22, parent[3] = 1, and a new entry {22, 3} is pushed. |
| 10 | L8 | u=2 dist[u]=9 | Pop {9, 2}: the distance to 2 is final (9). Relax its edges. |
| 11 | L10 | u=2 v=0 candidate=18 | 2 → 0: 9 + 9 = 18 does not beat 0. |
| 12 | L10 | u=2 v=1 candidate=19 | 2 → 1: 9 + 10 = 19 does not beat 7. |
| 13 | L12 | u=2 v=3 dist[v]=20 | 2 → 3: 9 + 11 = 20. dist[3] = 20, parent[3] = 2, and a new entry {20, 3} is pushed; the older, larger entry for 3 stays in the queue. |
| 14 | L12 | u=2 v=5 dist[v]=11 | 2 → 5: 9 + 2 = 11. dist[5] = 11, parent[5] = 2, and a new entry {11, 5} is pushed; the older, larger entry for 5 stays in the queue. |
| 15 | L8 | u=5 dist[u]=11 | Pop {11, 5}: the distance to 5 is final (11). Relax its edges. |
| 16 | L10 | u=5 v=0 candidate=25 | 5 → 0: 11 + 14 = 25 does not beat 0. |
| 17 | L10 | u=5 v=2 candidate=13 | 5 → 2: 11 + 2 = 13 does not beat 9. |
| 18 | L12 | u=5 v=4 dist[v]=20 | 5 → 4: 11 + 9 = 20. dist[4] = 20, parent[4] = 5, and a new entry {20, 4} is pushed. |
| 19 | L9 | entry=14,5 dist[v]=11 | Pop {14, 5}, but dist[5] is already 11: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 20 | L8 | u=3 dist[u]=20 | Pop {20, 3}: the distance to 3 is final (20). Relax its edges. |
| 21 | L10 | u=3 v=1 candidate=35 | 3 → 1: 20 + 15 = 35 does not beat 7. |
| 22 | L10 | u=3 v=2 candidate=31 | 3 → 2: 20 + 11 = 31 does not beat 9. |
| 23 | L10 | u=3 v=4 candidate=26 | 3 → 4: 20 + 6 = 26 does not beat 20. |
| 24 | L8 | u=4 dist[u]=20 | Pop {20, 4}: the distance to 4 is final (20). Relax its edges. |
| 25 | L10 | u=4 v=3 candidate=26 | 4 → 3: 20 + 6 = 26 does not beat 20. |
| 26 | L10 | u=4 v=5 candidate=29 | 4 → 5: 20 + 9 = 29 does not beat 11. |
| 27 | L9 | entry=22,3 dist[v]=20 | Pop {22, 3}, but dist[3] is already 20: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 28 | L15 | return=[0, 7, 9, 20, 20, 11] pushed=8 skipped=2 | Distances: [0, 7, 9, 20, 20, 11] (∞ = unreachable). 8 entries pushed, 2 skipped as outdated; each arrow pushes at most one entry (an undirected edge is two arrows), so the cost is O(m log m). Following parents back from 4 gives the path 0 → 2 → 5 → 4 with total weight 20. |
Lazy outdated entries
When a vertex's estimate improves, its old queue entry is still there with the larger distance. **Lazy deletion** leaves it: when it is eventually popped, top.distance > dist[v] shows that the vertex was already handled with a smaller value, and the entry is skipped. Every entry is pushed once and popped once.
So the queue holds at most m + 1 entries, and the number of skipped entries equals the number of pushes minus the number of settled vertices. Run the second preset, *Outdated entries appear*, to see one entry marked as skipped.
1static long[] queueStats(int n, List<int[]>[] adj, int s) { // {entries pushed, entries popped, outdated entries skipped}2long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n];4Arrays.fill(dist, INF);5dist[s] = 0;6PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0]));7pq.add(new long[]{0, s});8long pushed = 1, popped = 0, stale = 0;9while (!pq.isEmpty()) {10long[] top = pq.poll();11popped++;12int u = (int) top[1];13if (top[0] > dist[u]) { stale++; continue; }14for (int[] e : adj[u])15if (dist[u] + e[1] < dist[e[0]]) {16dist[e[0]] = dist[u] + e[1];17pq.add(new long[]{dist[e[0]], e[0]});18pushed++;19}20}21return new long[]{pushed, popped, stale};22}
Dijkstra with a priority queue: lazy deletion and path reconstruction
Interactive · edit the inputundirected graph · 6 vertices · 9 edges
d=0
queue {dist, vertex}, smallest first
dist[v]
Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it.
1long[] dijkstra(int n, List<int[]>[] adj, int s, int[] parent) { // adj[u] = {v, w}, all w >= 02long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n]; Arrays.fill(dist, INF); dist[s] = 0;4Arrays.fill(parent, -1);5PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0])); // {distance, vertex}6pq.add(new long[]{0, s});7while (!pq.isEmpty()) {8long[] top = pq.poll(); int u = (int) top[1];9if (top[0] > dist[u]) continue; // outdated entry: u was already improved10for (int[] e : adj[u]) {11if (dist[u] + e[1] >= dist[e[0]]) continue;12dist[e[0]] = dist[u] + e[1]; parent[e[0]] = u;13pq.add(new long[]{dist[e[0]], e[0]}); // lazy: the old entry stays in the queue14}15}16return dist;17}18List<Integer> pathTo(int[] parent, int t) { // follow parents back from t19LinkedList<Integer> p = new LinkedList<>();20for (int v = t; v != -1; v = parent[v]) p.addFirst(v);21return p;22}
Live state
source
0
Execution trace (dry run table)Show 28 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L6 | source=0 | Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it. |
| 2 | L8 | u=0 dist[u]=0 | Pop {0, 0}: the distance to 0 is final (0). Relax its edges. |
| 3 | L12 | u=0 v=1 dist[v]=7 | 0 → 1: 0 + 7 = 7. dist[1] = 7, parent[1] = 0, and a new entry {7, 1} is pushed. |
| 4 | L12 | u=0 v=2 dist[v]=9 | 0 → 2: 0 + 9 = 9. dist[2] = 9, parent[2] = 0, and a new entry {9, 2} is pushed. |
| 5 | L12 | u=0 v=5 dist[v]=14 | 0 → 5: 0 + 14 = 14. dist[5] = 14, parent[5] = 0, and a new entry {14, 5} is pushed. |
| 6 | L8 | u=1 dist[u]=7 | Pop {7, 1}: the distance to 1 is final (7). Relax its edges. |
| 7 | L10 | u=1 v=0 candidate=14 | 1 → 0: 7 + 7 = 14 does not beat 0. |
| 8 | L10 | u=1 v=2 candidate=17 | 1 → 2: 7 + 10 = 17 does not beat 9. |
| 9 | L12 | u=1 v=3 dist[v]=22 | 1 → 3: 7 + 15 = 22. dist[3] = 22, parent[3] = 1, and a new entry {22, 3} is pushed. |
| 10 | L8 | u=2 dist[u]=9 | Pop {9, 2}: the distance to 2 is final (9). Relax its edges. |
| 11 | L10 | u=2 v=0 candidate=18 | 2 → 0: 9 + 9 = 18 does not beat 0. |
| 12 | L10 | u=2 v=1 candidate=19 | 2 → 1: 9 + 10 = 19 does not beat 7. |
| 13 | L12 | u=2 v=3 dist[v]=20 | 2 → 3: 9 + 11 = 20. dist[3] = 20, parent[3] = 2, and a new entry {20, 3} is pushed; the older, larger entry for 3 stays in the queue. |
| 14 | L12 | u=2 v=5 dist[v]=11 | 2 → 5: 9 + 2 = 11. dist[5] = 11, parent[5] = 2, and a new entry {11, 5} is pushed; the older, larger entry for 5 stays in the queue. |
| 15 | L8 | u=5 dist[u]=11 | Pop {11, 5}: the distance to 5 is final (11). Relax its edges. |
| 16 | L10 | u=5 v=0 candidate=25 | 5 → 0: 11 + 14 = 25 does not beat 0. |
| 17 | L10 | u=5 v=2 candidate=13 | 5 → 2: 11 + 2 = 13 does not beat 9. |
| 18 | L12 | u=5 v=4 dist[v]=20 | 5 → 4: 11 + 9 = 20. dist[4] = 20, parent[4] = 5, and a new entry {20, 4} is pushed. |
| 19 | L9 | entry=14,5 dist[v]=11 | Pop {14, 5}, but dist[5] is already 11: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 20 | L8 | u=3 dist[u]=20 | Pop {20, 3}: the distance to 3 is final (20). Relax its edges. |
| 21 | L10 | u=3 v=1 candidate=35 | 3 → 1: 20 + 15 = 35 does not beat 7. |
| 22 | L10 | u=3 v=2 candidate=31 | 3 → 2: 20 + 11 = 31 does not beat 9. |
| 23 | L10 | u=3 v=4 candidate=26 | 3 → 4: 20 + 6 = 26 does not beat 20. |
| 24 | L8 | u=4 dist[u]=20 | Pop {20, 4}: the distance to 4 is final (20). Relax its edges. |
| 25 | L10 | u=4 v=3 candidate=26 | 4 → 3: 20 + 6 = 26 does not beat 20. |
| 26 | L10 | u=4 v=5 candidate=29 | 4 → 5: 20 + 9 = 29 does not beat 11. |
| 27 | L9 | entry=22,3 dist[v]=20 | Pop {22, 3}, but dist[3] is already 20: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 28 | L15 | return=[0, 7, 9, 20, 20, 11] pushed=8 skipped=2 | Distances: [0, 7, 9, 20, 20, 11] (∞ = unreachable). 8 entries pushed, 2 skipped as outdated; each arrow pushes at most one entry (an undirected edge is two arrows), so the cost is O(m log m). Following parents back from 4 gives the path 0 → 2 → 5 → 4 with total weight 20. |
Indexed heap
An **indexed heap** keeps at most one entry per vertex and remembers where each vertex sits in the heap array (pos[v]). When an estimate improves, it **decreases the key** in place by sifting the entry up, in O(log n). The heap then never holds more than n entries.
This saves memory and skips the stale pops, at the price of more code. With a binary heap the total time is still O((n + m) log n); the gain is in the constant factor and in memory. A Fibonacci heap reaches O(m + n log n) in theory but is rarely faster in practice.
1static class IndexedMinHeap { // one entry per vertex; decrease-key in O(log n)2private final int[] heap, pos; // heap[i] = vertex in slot i; pos[v] = slot of v, -1 if absent3private final long[] key;4private int size = 0;5IndexedMinHeap(int n) { heap = new int[n]; pos = new int[n]; key = new long[n]; Arrays.fill(pos, -1); }6boolean isEmpty() { return size == 0; }7void pushOrDecrease(int v, long k) {8if (pos[v] == -1) { key[v] = k; pos[v] = size; heap[size++] = v; siftUp(pos[v]); }9else if (k < key[v]) { key[v] = k; siftUp(pos[v]); }10}11int popMin() {12int top = heap[0];13heap[0] = heap[--size];14pos[heap[0]] = 0;15pos[top] = -1;16if (size > 0) siftDown(0);17return top;18}19long keyOf(int v) { return key[v]; }20private void siftUp(int i) {21while (i > 0 && key[heap[(i - 1) / 2]] > key[heap[i]]) { swap(i, (i - 1) / 2); i = (i - 1) / 2; }22}23private void siftDown(int i) {24while (true) {25int l = 2 * i + 1, r = l + 1, m = i;26if (l < size && key[heap[l]] < key[heap[m]]) m = l;27if (r < size && key[heap[r]] < key[heap[m]]) m = r;28if (m == i) return;29swap(i, m);30i = m;31}32}33private void swap(int a, int b) { int t = heap[a]; heap[a] = heap[b]; heap[b] = t; pos[heap[a]] = a; pos[heap[b]] = b; }34static long[] dijkstra(int n, List<int[]>[] adj, int s) {35long INF = Long.MAX_VALUE / 2;36long[] dist = new long[n];37Arrays.fill(dist, INF);38dist[s] = 0;39IndexedMinHeap heap = new IndexedMinHeap(n);40heap.pushOrDecrease(s, 0);41while (!heap.isEmpty()) {42int u = heap.popMin();43for (int[] e : adj[u])44if (dist[u] + e[1] < dist[e[0]]) { dist[e[0]] = dist[u] + e[1]; heap.pushOrDecrease(e[0], dist[e[0]]); }45}46return dist;47}48}
Path reconstruction
Record parent[v] = u whenever an edge u → v lowers dist[v]. When the algorithm ends, parent holds the shortest-path tree. Walk from the target back through parents to the source and reverse the list. If the target's distance is still infinite, there is no path and the result is empty.
Give the animation a destination vertex and the last frame lists the path. For a single target you may stop the search as soon as the target is popped, because its distance is final at that point.
1static List<Integer> shortestPath(int n, List<int[]>[] adj, int s, int t) { // empty when t is unreachable2long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n];4int[] parent = new int[n];5Arrays.fill(dist, INF);6Arrays.fill(parent, -1);7dist[s] = 0;8PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0]));9pq.add(new long[]{0, s});10while (!pq.isEmpty()) {11long[] top = pq.poll();12int u = (int) top[1];13if (top[0] > dist[u]) continue;14if (u == t) break; // the target is settled: its distance is final15for (int[] e : adj[u])16if (dist[u] + e[1] < dist[e[0]]) {17dist[e[0]] = dist[u] + e[1];18parent[e[0]] = u;19pq.add(new long[]{dist[e[0]], e[0]});20}21}22LinkedList<Integer> path = new LinkedList<>();23if (dist[t] >= INF) return path;24for (int v = t; v != -1; v = parent[v]) path.addFirst(v);25return path;26}
Dijkstra with a priority queue: lazy deletion and path reconstruction
Interactive · edit the inputundirected graph · 6 vertices · 9 edges
d=0
queue {dist, vertex}, smallest first
dist[v]
Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it.
1long[] dijkstra(int n, List<int[]>[] adj, int s, int[] parent) { // adj[u] = {v, w}, all w >= 02long INF = Long.MAX_VALUE / 2;3long[] dist = new long[n]; Arrays.fill(dist, INF); dist[s] = 0;4Arrays.fill(parent, -1);5PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0])); // {distance, vertex}6pq.add(new long[]{0, s});7while (!pq.isEmpty()) {8long[] top = pq.poll(); int u = (int) top[1];9if (top[0] > dist[u]) continue; // outdated entry: u was already improved10for (int[] e : adj[u]) {11if (dist[u] + e[1] >= dist[e[0]]) continue;12dist[e[0]] = dist[u] + e[1]; parent[e[0]] = u;13pq.add(new long[]{dist[e[0]], e[0]}); // lazy: the old entry stays in the queue14}15}16return dist;17}18List<Integer> pathTo(int[] parent, int t) { // follow parents back from t19LinkedList<Integer> p = new LinkedList<>();20for (int v = t; v != -1; v = parent[v]) p.addFirst(v);21return p;22}
Live state
source
0
Execution trace (dry run table)Show 28 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L6 | source=0 | Start with {0, 0} in the priority queue. The queue always gives the unsettled vertex with the smallest known distance; with non-negative weights nothing found later can beat it. |
| 2 | L8 | u=0 dist[u]=0 | Pop {0, 0}: the distance to 0 is final (0). Relax its edges. |
| 3 | L12 | u=0 v=1 dist[v]=7 | 0 → 1: 0 + 7 = 7. dist[1] = 7, parent[1] = 0, and a new entry {7, 1} is pushed. |
| 4 | L12 | u=0 v=2 dist[v]=9 | 0 → 2: 0 + 9 = 9. dist[2] = 9, parent[2] = 0, and a new entry {9, 2} is pushed. |
| 5 | L12 | u=0 v=5 dist[v]=14 | 0 → 5: 0 + 14 = 14. dist[5] = 14, parent[5] = 0, and a new entry {14, 5} is pushed. |
| 6 | L8 | u=1 dist[u]=7 | Pop {7, 1}: the distance to 1 is final (7). Relax its edges. |
| 7 | L10 | u=1 v=0 candidate=14 | 1 → 0: 7 + 7 = 14 does not beat 0. |
| 8 | L10 | u=1 v=2 candidate=17 | 1 → 2: 7 + 10 = 17 does not beat 9. |
| 9 | L12 | u=1 v=3 dist[v]=22 | 1 → 3: 7 + 15 = 22. dist[3] = 22, parent[3] = 1, and a new entry {22, 3} is pushed. |
| 10 | L8 | u=2 dist[u]=9 | Pop {9, 2}: the distance to 2 is final (9). Relax its edges. |
| 11 | L10 | u=2 v=0 candidate=18 | 2 → 0: 9 + 9 = 18 does not beat 0. |
| 12 | L10 | u=2 v=1 candidate=19 | 2 → 1: 9 + 10 = 19 does not beat 7. |
| 13 | L12 | u=2 v=3 dist[v]=20 | 2 → 3: 9 + 11 = 20. dist[3] = 20, parent[3] = 2, and a new entry {20, 3} is pushed; the older, larger entry for 3 stays in the queue. |
| 14 | L12 | u=2 v=5 dist[v]=11 | 2 → 5: 9 + 2 = 11. dist[5] = 11, parent[5] = 2, and a new entry {11, 5} is pushed; the older, larger entry for 5 stays in the queue. |
| 15 | L8 | u=5 dist[u]=11 | Pop {11, 5}: the distance to 5 is final (11). Relax its edges. |
| 16 | L10 | u=5 v=0 candidate=25 | 5 → 0: 11 + 14 = 25 does not beat 0. |
| 17 | L10 | u=5 v=2 candidate=13 | 5 → 2: 11 + 2 = 13 does not beat 9. |
| 18 | L12 | u=5 v=4 dist[v]=20 | 5 → 4: 11 + 9 = 20. dist[4] = 20, parent[4] = 5, and a new entry {20, 4} is pushed. |
| 19 | L9 | entry=14,5 dist[v]=11 | Pop {14, 5}, but dist[5] is already 11: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 20 | L8 | u=3 dist[u]=20 | Pop {20, 3}: the distance to 3 is final (20). Relax its edges. |
| 21 | L10 | u=3 v=1 candidate=35 | 3 → 1: 20 + 15 = 35 does not beat 7. |
| 22 | L10 | u=3 v=2 candidate=31 | 3 → 2: 20 + 11 = 31 does not beat 9. |
| 23 | L10 | u=3 v=4 candidate=26 | 3 → 4: 20 + 6 = 26 does not beat 20. |
| 24 | L8 | u=4 dist[u]=20 | Pop {20, 4}: the distance to 4 is final (20). Relax its edges. |
| 25 | L10 | u=4 v=3 candidate=26 | 4 → 3: 20 + 6 = 26 does not beat 20. |
| 26 | L10 | u=4 v=5 candidate=29 | 4 → 5: 20 + 9 = 29 does not beat 11. |
| 27 | L9 | entry=22,3 dist[v]=20 | Pop {22, 3}, but dist[3] is already 20: this entry is outdated (a shorter route was found after it was pushed). Skip it. Skipping instead of deleting is called lazy deletion. |
| 28 | L15 | return=[0, 7, 9, 20, 20, 11] pushed=8 skipped=2 | Distances: [0, 7, 9, 20, 20, 11] (∞ = unreachable). 8 entries pushed, 2 skipped as outdated; each arrow pushes at most one entry (an undirected edge is two arrows), so the cost is O(m log m). Following parents back from 4 gives the path 0 → 2 → 5 → 4 with total weight 20. |
Practice ladder
Now try it yourself
Easy
- Network Delay Time
Medium
- Path with Maximum Probability
- Path With Minimum Effort
- Cheapest Flights Within K Stops
- Swim in Rising Water
Advanced
- Minimum Cost to Reach Destination in Time
- Shortest Path Visiting All Nodes
- Reachable Nodes In Subdivided Graph
Key takeaways
- Dijkstra settles the closest unsettled vertex; this is safe only when no weight is negative.
- With a binary heap and lazy deletion the cost is O((n + m) log n); the quadratic version suits dense graphs.
- Outdated entries are skipped when popped; pushes equal pops.
- An indexed heap supports decrease-key and holds one entry per vertex.
- Parent links give the path; stop early when the target is popped.