Topics › Graphs › Shortest Path Algorithms and Problems
Djisktra's Algorithm
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an undirected weighted graph with V vertices numbered 0 to V - 1 and a list of edges {u, v, w}, each an edge between u and v of weight w ≥ 0. Given a source vertex src, return the length of the shortest path from src to every vertex. The original statement guarantees a connected graph; the code here also handles disconnected graphs by reporting -1 for unreachable vertices.
Constraints: 1 ≤ V ≤ 10^5, 0 ≤ w ≤ 1000, no negative weights. (The sheet spells the name “Djisktra”; the algorithm is Edsger Dijkstra’s.)
Example 1
V = 3, src = 2, edges = [0-1 (1), 0-2 (6), 1-2 (3)]
answer = [4, 3, 0] // 2 → 1 → 0 costs 3 + 1 = 4, cheaper than the direct edge of 6
Example 2
V = 6, src = 0
edges = [0-1 (4), 0-2 (4), 1-2 (2), 2-3 (3), 2-4 (1), 2-5 (6), 3-5 (2), 4-5 (3)]
answer = [0, 4, 4, 7, 5, 8] // node 5 via 0-2-4-5 = 4 + 1 + 3
Every approach below stores the graph as an adjacency list of {neighbour, weight} pairs, with each undirected edge added in both directions, and finishes by turning the “infinity” marker into -1:
static List<List<int[]>> buildAdj(int V, int[][] edges) {
List<List<int[]>> adj = new ArrayList<>();
for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
for (int[] e : edges) {
adj.get(e[0]).add(new int[]{e[1], e[2]});
adj.get(e[1]).add(new int[]{e[0], e[2]});
}
return adj;
}
/** Replaces the "infinity" marker with -1 for unreachable nodes. */
static int[] finish(int[] dist) {
for (int i = 0; i < dist.length; i++) if (dist[i] == Integer.MAX_VALUE) dist[i] = -1;
return dist;
}
Approach 1: Brute force, relax until stable
Intuition. Shortest distances satisfy dist[v] ≤ dist[u] + w for every edge. Start from dist[src] = 0 and infinity elsewhere, and keep sweeping over all edges, lowering any dist[v] that breaks the rule, until a full sweep changes nothing. Each sweep makes at least one more edge of every shortest path correct, so at most V - 1 productive sweeps happen.
Algorithm.
- Initialise
dist. - While the previous sweep changed something: for every edge, in both directions, relax it if the tail is reachable.
- Return
dist, with infinity mapped to-1.
static int[] bruteForce(int V, int[][] edges, int src) {
int[] dist = new int[V];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
boolean changed = true;
while (changed) {
changed = false;
for (int[] e : edges) {
for (int d = 0; d < 2; d++) {
int u = e[d], v = e[1 - d];
if (dist[u] != Integer.MAX_VALUE && dist[u] + e[2] < dist[v]) {
dist[v] = dist[u] + e[2];
changed = true;
}
}
}
}
return finish(dist);
}
Complexity. Time O(V × E): up to V sweeps, each over all edges. Space O(V). This is Bellman-Ford, and it is wasteful here because it never uses the fact that the weights are non-negative.
Approach 2: Better, Dijkstra with an array
Intuition. Split the vertices into finished ones, whose distance is known for sure, and the rest, which only have a tentative distance through finished vertices. The greedy step is: the unfinished vertex u with the smallest tentative distance is already correct.
Why? Consider any path from src to u. It starts inside the finished set and must leave it at some first unfinished vertex x. The part of the path up to x costs at least dist[x], and dist[x] ≥ dist[u] because u was chosen as the smallest. The rest of the path, from x to u, has non-negative weights and cannot make it cheaper. So no path beats dist[u], and u can be marked finished. Then we relax u’s edges, which may lower the tentative distances of its neighbours, and repeat.
The simplest way to find the smallest unfinished vertex is a linear scan.
Algorithm.
- Initialise
dist, anddone[]as all false. - Repeat
Vtimes: scan for the unfinished vertexuwith the smallest finite distance. If there is none, stop. - Mark
udone and relax each edgeu - vwithvnot done.
static int[] dijkstraArray(int V, int[][] edges, int src) {
List<List<int[]>> adj = buildAdj(V, edges);
int[] dist = new int[V];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
boolean[] done = new boolean[V];
for (int iter = 0; iter < V; iter++) {
int u = -1;
for (int i = 0; i < V; i++) {
if (!done[i] && dist[i] != Integer.MAX_VALUE && (u == -1 || dist[i] < dist[u])) u = i;
}
if (u == -1) break; // every remaining node is unreachable
done[u] = true; // dist[u] is final from here on
for (int[] e : adj.get(u)) {
int v = e[0], w = e[1];
if (!done[v] && dist[u] + w < dist[v]) dist[v] = dist[u] + w;
}
}
return finish(dist);
}
Complexity. Time O(V^2 + E): V scans of V vertices, and each edge is relaxed a constant number of times. Space O(V + E). This version is actually the best choice for dense graphs where E is close to V^2, because then the heap’s log factor buys nothing.
Approach 3: Optimal, Dijkstra with a priority queue
Intuition. For sparse graphs, the linear scan dominates. Replace it with a min-heap of {distance, vertex} pairs so the closest vertex is found in O(log V).
Java’s PriorityQueue cannot lower the key of an element already inside it. The standard workaround is lazy deletion: when dist[v] improves, push a new pair {dist[v], v} and leave the old one in the heap. When an old pair is popped later, its distance is larger than dist[v], so it is stale and we skip it. The first pair popped for each vertex carries its final distance, by the same greedy argument as in Approach 2.
Algorithm.
- Initialise
dist, push{0, src}. - Pop the pair
{d, u}with the smallestd. Ifd > dist[u], skip it. - For each edge
u - vof weightw, ifd + w < dist[v], setdist[v] = d + wand push{dist[v], v}. - When the heap is empty,
distis final.
static int[] optimal(int V, int[][] edges, int src) {
List<List<int[]>> adj = buildAdj(V, edges);
int[] dist = new int[V];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
pq.add(new int[]{0, src});
while (!pq.isEmpty()) {
int[] top = pq.poll();
int d = top[0], u = top[1];
if (d > dist[u]) continue; // stale entry: u was settled with a smaller distance
for (int[] e : adj.get(u)) {
int v = e[0], w = e[1];
if (d + w < dist[v]) {
dist[v] = d + w;
pq.add(new int[]{dist[v], v});
}
}
}
return finish(dist);
}
Dry run on Example 2 from src = 0. Pairs with equal distance may pop in either order; the result is the same.
| pop | action | pushed |
|---|---|---|
| (0, 0) | dist1 = 4, dist2 = 4 | (4, 1), (4, 2) |
| (4, 1) | 0 and 2 are not improved | none |
| (4, 2) | dist3 = 7, dist4 = 5, dist5 = 10 | (7, 3), (5, 4), (10, 5) |
| (5, 4) | dist5 = 8 | (8, 5) |
| (7, 3) | 5 via 3 would be 9, no change | none |
| (8, 5) | no neighbour improves | none |
| (10, 5) | stale: 10 > dist5 = 8, skipped | none |
Result [0, 4, 4, 7, 5, 8]. The pair (10, 5) shows lazy deletion at work: it was made obsolete by (8, 5) and is discarded in O(1) after being popped.
Complexity. Each vertex is expanded once (later copies are stale), so each direction of each edge is relaxed at most once and the heap receives at most 2E + 1 pairs. Each push or pop costs O(log E) = O(log V) because E ≤ V^2. Total time O((V + E) log V). Space O(V + E) for the graph, distances and heap.
Edge cases and pitfalls
- Negative weights. The greedy step relies on “the rest of the path cannot make it cheaper”. A negative edge breaks that, and Dijkstra can return wrong answers. Use Bellman-Ford instead.
- Overflow.
d + wis computed inint. WithVup to10^5andw ≤ 1000the largest distance is about10^8, which fits. For larger weights uselongand alonginfinity. - Forgetting the stale check. Without
if (d > dist[u]) continue;the answer is still correct, but outdated pairs re-scan their vertex’s edges, and the running time can blow up on dense graphs. - Unreachable vertices. Their distance stays at infinity and is reported as
-1. The array version must also stop when no finite distance is left, otherwise it would “finish” an unreachable vertex with distance infinity. - Zero-weight edges and parallel edges. Both are fine; the tests include a path of zero-weight edges and three parallel edges between the same two nodes.
- Undirected input. Add every edge in both directions in the adjacency list.
Related problems
- Why priority Queue is used in Djisktra’s Algorithm compares the heap with a plain queue and an ordered set.
- Shortest path in undirected graph with unit weights is the special case where BFS is enough.
- Shortest path in DAG is linear and allows negative weights when the graph is acyclic.
- Bellman Ford Algorithm handles negative weights.
- Path with minimum effort runs Dijkstra with “max” in place of “sum”.
- Network Delay Time is a direct application on a directed graph.