Shortest path: Dijkstra
BFS finds the shortest path in an unweighted graph because there "shortest" = "fewest steps". The moment weights appear, that equality falls apart.
A simple counter-example: there are two routes from A to C — the direct edge A → C (weight 10) and the path A → B → C (weight 1 + 1 = 2). BFS picks the one-step direct edge and returns 10, while the real shortest path costs 2. BFS counts steps, not cost.
So why not "split every edge into weight pieces and run BFS anyway"? In theory you can, but a weight of 1000 turns one edge into 1000 artificial nodes — it does not survive contact with real data.
The right answer is Dijkstra's algorithm: it visits neighbours in order of "cheapest so far" rather than "fewest steps". That requires a priority queue (a min-heap) instead of a plain queue — you always pop the node with the smallest current distance.
Dijkstra rests on three ideas.
1. The `dist` table — for each node, "the cheapest distance from the source found so far". Initially the source is 0 and everything else is infinity.
2. Relaxation — looking from u at v you ask: "is dist[u] + weight(u,v) smaller than the current dist[v]?" If it is, you update dist[v] and push v into the priority queue. The whole algorithm is that one line repeated.
3. The greedy invariant — all of the correctness hangs on this: the distance of a node popped from the priority queue is already final and will never improve. The reason: every remaining route starts from a distance at least as large, and since weights are non-negative a path can only get longer.
Two practical details that must be visible in the code:
- Stale entries: the same node can enter the queue several times because a plain heap has no "decrease-key". So if a popped entry's distance is larger than the one in
dist, you simply drop it (if (d > dist[u]) continue). This is called lazy deletion, and interviewers do look for that line - Returning the path itself: if you need the route and not just the cost, keep a
prev[v] = utable and walk back from the target at the end
Complexity: with a binary heap O((V + E) log V), usually written O(E log V). Memory O(V).
Negative weights break Dijkstra, and this is the most frequent follow-up question.
The reason lies in the greedy invariant: the moment Dijkstra pops a node it declares its distance final. With a negative edge, a longer path discovered later can reduce that distance — but the algorithm never revisits the node. The result is silently wrong; nothing throws, which is what makes it dangerous.
Counter-example: A → B weight 5, A → C weight 2, C → B weight −4. Dijkstra may finalise C at 2 and then B at 5, while the true answer is 2 + (−4) = −2.
For that case you use Bellman-Ford: it relaxes every edge V − 1 times, at O(V × E) — considerably slower than Dijkstra, but correct with negative weights. It also detects a negative cycle if any improvement is still possible on the V-th iteration (in which case "shortest path" stops being meaningful, since each lap around the cycle lowers the cost).
One clarification: negative weights don't always mean an exotic problem — currency arbitrage, energy balances and profit/cost models are real examples.
| Algorithm | When it is used | Complexity | Limitation |
|---|---|---|---|
| BFS | No weights (or all equal) | O(V + E) | Gives a wrong answer with weights |
| Dijkstra | Non-negative weights, single source to all | O((V + E) log V) — with a binary heap | Silently incorrect with negative weights |
| Bellman-Ford | Negative weights present; need negative-cycle detection | O(V × E) | Considerably slower |
| Floyd-Warshall | All-pairs distances on a small graph | O(V³) | Not applicable as V grows |
| A* | One specific target plus a good heuristic (maps, games) | Faster than Dijkstra in practice | The heuristic must be admissible or the result isn't optimal |
Interview tip: you are not expected to have Dijkstra memorised — you are expected to explain why it works. Three sentences are enough: "The priority queue hands me the cheapest node; when I pop it, its distance is final, because every remaining route starts from a larger distance and no weight is negative; so each node is finalised exactly once." If you can say that, forgetting a detail of the code is fine. If you can't, perfect code won't save you — the next question is always "what changes with a negative weight?"