Shortest Paths
Choosing the algorithm
Four algorithms cover essentially every shortest-path problem. Which one you need is decided by the edge weights and the number of sources — not by preference.
Which shortest-path algorithm
| Situation | Algorithm | Cost |
|---|---|---|
| All edges weight 1 | BFS | O(V + E) |
| All edges weight 0 or 1 | 0-1 BFS with a deque | O(V + E) |
| Non-negative weights, one source | Dijkstra with a heap | O(E log V) |
| Negative weights allowed, one source | Bellman–Ford | O(V · E) |
| All pairs, small graph | Floyd–Warshall | O(V3) |
The first row is worth emphasising: if every edge costs the same, Dijkstra is unnecessary overhead. Plain BFS is simpler and faster. See Graph Representation and Traversal.
Dijkstra
For non-negative weights from a single source. Repeatedly settle the closest unsettled node, then relax its outgoing edges.
static class Edge {
int to;
long weight;
Edge(int to, long weight) { this.to = to; this.weight = weight; }
}
static long[] dijkstra(List<List<Edge>> adj, int source, int n) {
long[] dist = new long[n + 1];
Arrays.fill(dist, Long.MAX_VALUE);
dist[source] = 0;
// {node, distance}, ordered by distance
PriorityQueue<long[]> pq =
new PriorityQueue<>((x, y) -> Long.compare(x[1], y[1]));
pq.add(new long[]{source, 0});
while (!pq.isEmpty()) {
long[] top = pq.poll();
int u = (int) top[0];
long d = top[1];
if (d > dist[u]) continue; // a stale copy — already settled better
for (Edge e : adj.get(u)) {
long nd = d + e.weight;
if (nd < dist[e.to]) {
dist[e.to] = nd;
pq.add(new long[]{e.to, nd});
}
}
}
return dist;
}
The stale-entry check is not optional
if (d > dist[u]) continue;Java’s PriorityQueue has no decrease-key operation, so the standard approach pushes a new entry whenever a distance improves and leaves the old one in the heap. That line discards the outdated copies.
Without it the algorithm still terminates with correct distances, but it re-expands nodes repeatedly and the runtime degrades badly on dense graphs.
Other details that matter:
distislongand initialised toLong.MAX_VALUE. UsingInteger.MAX_VALUEwithintoverflows the moment you add a weight to it.- Test
nd < dist[e.to]before assigning, not after. - Unreachable nodes keep
Long.MAX_VALUE. Check for that before printing rather than printing a huge number.
Dijkstra requires non-negative weights. With a negative edge it can settle a node too early and never revisit it, producing a wrong answer with no error.
0-1 BFS
When every weight is 0 or 1, a deque replaces the heap and the log factor disappears. Push zero-weight moves to the front and one-weight moves to the back, which keeps the deque sorted by distance automatically.
static int[] zeroOneBfs(List<List<Edge>> adj, int source, int n) {
int[] dist = new int[n + 1];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[source] = 0;
Deque<Integer> dq = new ArrayDeque<>();
dq.addFirst(source);
while (!dq.isEmpty()) {
int u = dq.pollFirst();
for (Edge e : adj.get(u)) {
int nd = dist[u] + (int) e.weight;
if (nd < dist[e.to]) {
dist[e.to] = nd;
if (e.weight == 0) dq.addFirst(e.to);
else dq.addLast(e.to);
}
}
}
return dist;
}
This turns up in grid problems where some moves are free and others cost one — for example, walking is free but breaking a wall costs one.
Bellman–Ford
Handles negative weights. Relax every edge V - 1 times; any distance that can still improve on an extra pass lies on a negative cycle.
static long[] bellmanFord(int[][] edges, int source, int n) {
long[] dist = new long[n + 1];
Arrays.fill(dist, Long.MAX_VALUE / 4); // headroom so adding cannot overflow
dist[source] = 0;
for (int iter = 0; iter < n - 1; iter++) {
boolean changed = false;
for (int[] e : edges) { // {from, to, weight}
if (dist[e[0]] + e[2] < dist[e[1]]) {
dist[e[1]] = dist[e[0]] + e[2];
changed = true;
}
}
if (!changed) break; // settled early
}
return dist;
}
Two practical notes. Initialising to Long.MAX_VALUE / 4 rather than Long.MAX_VALUE means dist[from] + weight cannot overflow for an unreachable node. And the changed flag often ends the loop far before V - 1 iterations.
To detect a negative cycle, run one more pass; if anything still improves, a negative cycle is reachable from the source.
Floyd–Warshall
All pairs at once, for small graphs. Three nested loops, with the intermediate node outermost.
static void floydWarshall(long[][] dist, int n) {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
The loop order is the algorithm
k must be the outermost loop. The invariant is that after iteration k, dist[i][j] is the shortest path using only nodes 1..k as intermediates. Putting k inside breaks that invariant and yields wrong answers on some graphs while looking correct on others.
O(V3) means V ≤ 500 comfortably, and V = 1000 is 109 operations — too slow in Java. Above that, run Dijkstra from each node instead.
Initialise the diagonal to 0, absent edges to a large sentinel, and use a sentinel with headroom so the addition cannot overflow.
Reconstructing the path
Distances alone are often not the answer. Record where each improvement came from.
int[] parent = new int[n + 1];
Arrays.fill(parent, -1);
// inside the relaxation:
if (nd < dist[e.to]) {
dist[e.to] = nd;
parent[e.to] = u;
pq.add(new long[]{e.to, nd});
}
// afterwards, walk backwards from the target
List<Integer> path = new ArrayList<>();
for (int at = target; at != -1; at = parent[at]) {
path.add(at);
}
Collections.reverse(path);
Common mistakes
- Dijkstra with negative edges. Silently wrong; use Bellman–Ford.
- Missing the stale-entry check, which makes Dijkstra far slower than its bound.
intdistances that overflow when a weight is added to the sentinel.- Floyd–Warshall with
knot outermost. - Printing the sentinel for an unreachable node instead of the required marker.
- Using Dijkstra when all weights are equal — BFS is simpler and faster.
- Building the adjacency list one-directional for an undirected graph.
Practice
Name the algorithm from the constraints before writing anything.
- Unweighted graph, one source, print distances to all nodes.
- Weighted graph with positive weights, up to 200,000 nodes and edges, one source.
- Graph with possibly negative weights, 1,000 nodes, detect whether a negative cycle exists.
- 300 nodes, print the shortest distance between every pair.
- Grid where moving to an adjacent cell is free along a road and costs 1 off-road; find the cheapest route across.
Hints
- BFS.
- Dijkstra with a heap.
longdistances. - Bellman–Ford, then one extra relaxation pass — if anything improves, a negative cycle is reachable. To find cycles anywhere in the graph rather than only those reachable from one source, initialise every distance to 0.
- Floyd–Warshall; 3003 is 2.7 × 107.
- 0-1 BFS with a deque.