Dijkstra’s algorithm is a generalization of BFS from unit-cost to weighted graphs. Instead of breadth-first, we’re now “best-first”. Instead of using a queue and always popping the next node in queue, we now use a priority queue and always pop the best node (in terms of distance from source) each time. Instead of using the popped node to push (unvisited) neighbors into the queue, we now use the popped node \(v\) to update neighbors \(u\) if possible.
| BFS | Dijkstra | |
|---|---|---|
| used for | unweighted graph | weighted graph |
| visit order | breadth-first | best-first |
| data structure | queue | PQ |
| always pop | the next node in queue | the best node in PQ |
| use popped node to | push (unvisited) neighbors to queue | update neighbors if possible |
decrease_keyPseudocode:
d = defaultdict(lambda : infinity) # distance estimate from s to each v
d[s] = 0
PQ = {(s, 0)} # start node; in practice: PQ = {s: 0}
while PQ not empty:
(v, dist) = PQ.pop() # best distance from s to v is now fixed
for (v, u, w) in v's outgoing edges:
if dist + w < d[u]: # can update?
d[u] = dist + w # new estimate
PQ.decrease_key(u, d[u]) # in practice: PQ[u] = d[u] Pseudocode:
d = defaultdict(lambda : infinity) # distance estimate from s to each v
d[s] = 0
PQ = {(s, 0)} # start node; in practice (heapq): PQ = [(0, s)]
popped = empty_set # black nodes
while PQ not empty:
(v, dist) = PQ.pop() # could be duplicate pop!
if v not in popped: # first time v is popped?
popped.add(v)
for (v, u, w) in v's outgoing edges:
if dist + w < d[u]: # can update?
d[u] = dist + w # new estimate
PQ.push(u, d[u]) # could be duplicate | standard | alternative | |
|---|---|---|
| method | decrease_key |
push duplicates |
| PQ size | \(O(V)\) | \(O(E)\) |
| number of pops | exactly \(V\) | at most \(E\) |
| … total pop time | \(O(V\log V)\) | \(O(E\log E)\) |
| number of pushes or updates | at most \(E\) | at most \(E\) |
| … total push/update time | \(O(E\log V)\) | \(O(E\log E)\) |
| total time | \(O((V+E)\log V)\) | \(O(E\log E)\) |