Dijkstra's shortest paths
- Module
- M6.1
- Lesson
- 1 of 1
- Reading time
- 5 min
In this lesson
- Explain why settling the closest node first needs non-negative edge weights.
- Implement Dijkstra's algorithm with Python's heapq module.
- Skip out-of-date heap entries instead of updating them.
- Store the graph and read input so the search runs fast on PyPy 3.8.
Breadth-first search finds shortest paths when every edge costs the same. Many graphs are not like that. Roads have lengths, tunnels have travel times, and a route with more edges can still be the cheaper one.
Take three nodes. The edge from A to B costs 4. The edge from A to C costs 1, and the edge from C to B costs 2. BFS would say B is one edge away from A, which is true. But the cheapest route to B goes through C, for a total of 3. These three nodes are the start of the graph in the step-through later in this lesson.
Dijkstra's algorithmAn algorithm that finds the shortest path from a start node to all other nodes in a graph with non-negative edge weights.In the glossary finds the cheapest distance from one start node to every other node, as long as no edge has a negative weight.
Settle the closest node first
The algorithm keeps a best-known distance for each node. At the start, the start node has distance 0 and every other node has no distance at all.
Then it repeats one step. Among the nodes that are not done, pick the one with the smallest best-known distance. Mark it done: its distance will never change again. Then look at each of its edges and see whether the edge gives a neighbour a shorter route.
Why is it safe to call that node done? Suppose the closest node that is not done has distance 5. Any other route to it must first pass through some other node that is not done, and that node is already at least 5 away. Every edge weight is zero or more, so continuing from there can only add. No other route can come in under 5.
Relaxing an edge
Checking an edge for a shorter route is called relaxing the edge. Say node u has just become done with distance d, and an edge of weight w leads to node v. The route through u reaches v with a total of d + w.
If v has no distance yet, or d + w is smaller than its current best, record d + w as the new best for v. Otherwise leave v alone.
In the first example, A is done at 0. Relaxing its edges gives B a best of 4 and C a best of 1. C is now the closest node that is not done, so it is done next. Relaxing the edge from C to B gives 1 + 2 = 3, which beats 4, so B's best drops to 3.
A heap finds the closest node
Scanning every node to find the closest one that is not done is slow on a large graph. A priority queueA data structure that retrieves the item with the smallest (or largest) priority key first.In the glossary hands you the smallest item quickly instead. Python's heapq module turns a plain list into a min-heap, where the smallest item always sits at index 0.
import heapq
heap = []heapq.heappush(heap, (4, "B"))heapq.heappush(heap, (1, "C"))heapq.heappush(heap, (3, "B"))print(heapq.heappop(heap))print(heapq.heappop(heap))Output
(1, 'C')
(3, 'B')The pairs go in as 4, 1 and 3 but come out smallest first. The heap stores (distance, node) pairs. Python compares pairs by their first item, so the pair with the smallest distance comes out first. Both heappush and heappop take time proportional to the logarithm of the heap's size.
Out-of-date entries
Look at that heap again. Node B is in it twice, once with 4 and once with 3. That happens every time a node's best distance improves: the new, smaller pair is pushed, and the old pair stays behind.
Many textbooks update the old entry in place instead. heapq has no way to do that quickly, because finding an entry inside the heap means searching the whole list.
So Python programs leave the old entries alone. The smaller pair always comes out first, and that node is marked done. When the older pair comes out later, the program sees that its node is already done and skips it. The skip costs one comparison.
import heapq
heap = []heapq.heappush(heap, (4, "B"))heapq.heappush(heap, (3, "B"))done = {"B": False}while heap: d, u = heapq.heappop(heap) if done[u]: print("skip", d, u) continue # an out-of-date entry: u was done earlier done[u] = True print("done", d, u)Output
done 3 B
skip 4 BHere B was pushed twice. The pair with 3 comes out first and marks B done. The pair with 4 comes out next and is skipped.
The whole program
The input gives the number of nodes and edges, then the node names, then one line per edge: two names and a weight. The first name is the start. The program prints the distance to every node, or -1 for a node it cannot reach.
import heapqimport sys
def main() -> None: data = sys.stdin.read().split() if not data: return n = int(data[0]) m = int(data[1]) names = data[2:2 + n] index = {name: i for i, name in enumerate(names)}
adj = [[] for _ in range(n)] pos = 2 + n for _ in range(m): u = index[data[pos]] v = index[data[pos + 1]] w = int(data[pos + 2]) pos += 3 adj[u].append((v, w)) adj[v].append((u, w))
best = [-1] * n # shortest distance found so far; -1 means none yet done = [False] * n # True once a node's distance is final best[0] = 0 heap = [(0, 0)] while heap: d, u = heapq.heappop(heap) if done[u]: continue # an out-of-date entry: u was done earlier done[u] = True for v, w in adj[u]: nd = d + w if not done[v] and (best[v] == -1 or nd < best[v]): best[v] = nd heapq.heappush(heap, (nd, v))
print(" ".join(str(x) for x in best))
if __name__ == "__main__": main()Input
5 6
A B C D E
A B 4
A C 1
C B 2
B D 1
C E 5
D E 1Output
0 3 1 4 5The graph is stored as an adjacency list. Each node's entry holds (neighbour, weight) pairs. Each edge is added in both directions, because these roads are two-way. best holds the best-known distances and done marks the nodes whose distance is final.
Step through the same graph below. Watch the heap in the right panel. Each time the smallest entry leaves it, one node becomes done. When the smallest entry belongs to a node that is already done, it is out of date and gets thrown away. Both presets have two of those steps.
(0, A) goes into the heap. Heap entries are (distance, node) pairs. Every other node is at ∞: no route to it is known yet.- Current
- Queued
- Done
- Answer path
- Invalid
Figure 1Shortest path search on a weighted graph using a min-heap
Read the steps as text
Shortest distances from A on a small weighted graph. The heap always hands back the entry with the smallest distance; that node becomes done, and each of its edges may give a neighbour a shorter distance, which goes into the heap as a new entry. Out-of-date entries are thrown away.
Five nodes
- A starts with distance 0, and the entry
(0, A)goes into the heap. Heap entries are(distance, node)pairs. Every other node is at ∞: no route to it is known yet. - The smallest entry is
(0, A). No shorter route to A can appear later, because every other entry is at least 0: A is done with distance 0. - Through A, B is 0 + 4 = 4 away, better than ∞.
(4, B)goes into the heap. - Through A, C is 0 + 1 = 1 away, better than ∞.
(1, C)goes into the heap. - The smallest entry is
(1, C). No shorter route to C can appear later, because every other entry is at least 1: C is done with distance 1. - Through C, B is 1 + 2 = 3 away, better than 4.
(3, B)goes into the heap. - Through C, E is 1 + 5 = 6 away, better than ∞.
(6, E)goes into the heap. - The smallest entry is
(3, B). No shorter route to B can appear later, because every other entry is at least 3: B is done with distance 3. - Through B, D is 3 + 1 = 4 away, better than ∞.
(4, D)goes into the heap. - The smallest entry is
(4, B), but B is already done with distance 3. This entry is out of date, so it is thrown away. - The smallest entry is
(4, D). No shorter route to D can appear later, because every other entry is at least 4: D is done with distance 4. - Through D, E is 4 + 1 = 5 away, better than 6.
(5, E)goes into the heap. - The smallest entry is
(5, E). No shorter route to E can appear later, because every other entry is at least 5: E is done with distance 5. - The smallest entry is
(6, E), but E is already done with distance 5. This entry is out of date, so it is thrown away. - Every reachable node has its final distance. The thick edges show the tree of shortest paths.
Six nodes
- A starts with distance 0, and the entry
(0, A)goes into the heap. Heap entries are(distance, node)pairs. Every other node is at ∞: no route to it is known yet. - The smallest entry is
(0, A). No shorter route to A can appear later, because every other entry is at least 0: A is done with distance 0. - Through A, B is 0 + 2 = 2 away, better than ∞.
(2, B)goes into the heap. - Through A, C is 0 + 5 = 5 away, better than ∞.
(5, C)goes into the heap. - The smallest entry is
(2, B). No shorter route to B can appear later, because every other entry is at least 2: B is done with distance 2. - Through B, C is 2 + 1 = 3 away, better than 5.
(3, C)goes into the heap. - Through B, D is 2 + 6 = 8 away, better than ∞.
(8, D)goes into the heap. - The smallest entry is
(3, C). No shorter route to C can appear later, because every other entry is at least 3: C is done with distance 3. - Through C, E is 3 + 2 = 5 away, better than ∞.
(5, E)goes into the heap. - The smallest entry is
(5, C), but C is already done with distance 3. This entry is out of date, so it is thrown away. - The smallest entry is
(5, E). No shorter route to E can appear later, because every other entry is at least 5: E is done with distance 5. - Through E, D is 5 + 1 = 6 away, better than 8.
(6, D)goes into the heap. - The smallest entry is
(6, D). No shorter route to D can appear later, because every other entry is at least 6: D is done with distance 6. - Through D, F is 6 + 3 = 9 away, better than ∞.
(9, F)goes into the heap. - The smallest entry is
(8, D), but D is already done with distance 6. This entry is out of date, so it is thrown away. - The smallest entry is
(9, F). No shorter route to F can appear later, because every other entry is at least 9: F is done with distance 9. - Every reachable node has its final distance. The thick edges show the tree of shortest paths.
Why negative edges break it
The safety argument above relied on every edge weight being zero or more. Take that away and the argument fails.
Suppose A has an edge to B of weight 2 and an edge to C of weight 5, and a one-way edge from C to B has weight -4. Dijkstra's algorithm marks B done at distance 2, because 2 is less than 5. But the route A to C to B costs 5 - 4 = 1. By the time the algorithm looks at C, B is already done and its distance is wrong.
If a problem allows negative weights, it needs a different algorithm. Check the constraints for the smallest possible weight before you choose Dijkstra.
Making it fast on PyPy
Senior problems often have around nodes and edges. The algorithm itself is quick enough. Say the graph has nodes and edges. Each edge can push at most one entry from each of its two ends. So the heap never holds more than about entries, and the total work grows like . How you read the input and store the graph decides the rest.
Read the whole input in one call with sys.stdin.read().split(). Then take tokens from that list by position. Calling input() hundreds of thousands of times is much slower.
Number the nodes from 0 and keep per-node data in plain lists, as the program does with adj, best and done. Indexing a list by an integer is faster than looking up a key in a dictionary, and PyPy's compiler makes those list reads very cheap. If node names are not numbers, convert them to numbers once while reading, as the program does with index.
Keep the loop body small. The program computes d + w once per edge and does all its checks with plain comparisons.
Practice
Try these on the judges. Each link opens the problem on WMOJ or DMOJ.
- 2025 S4Floor is Lava (opens on WMOJ in a new tab) WMOJ
Find the cheapest way through a set of rooms and tunnels of different temperatures.
- 2023 S4Minimum Cost Roads (opens on WMOJ in a new tab) WMOJ
Keep the cheapest set of roads that leaves every shortest distance unchanged.
- 2015 S4Convex Hull (opens on DMOJ in a new tab) DMOJ
Find the fastest crossing between islands while keeping hull damage under a limit.
Why DMOJ: An older weighted shortest-path problem that still makes good practice for this module.