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_key
Pseudocode:
= defaultdict(lambda : infinity) # distance estimate from s to each v
d = 0
d[s] = {(s, 0)} # start node; in practice: PQ = {s: 0}
PQ while PQ not empty:
= PQ.pop() # best distance from s to v is now fixed
(v, dist) for (v, u, w) in v's outgoing edges:
if dist + w < d[u]: # can update?
= dist + w # new estimate
d[u] # in practice: PQ[u] = d[u] PQ.decrease_key(u, d[u])
Pseudocode:
= defaultdict(lambda : infinity) # distance estimate from s to each v
d = 0
d[s] = {(s, 0)} # start node; in practice (heapq): PQ = [(0, s)]
PQ = empty_set # black nodes
popped while PQ not empty:
= PQ.pop() # could be duplicate pop!
(v, dist) 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?
= dist + w # new estimate
d[u] # could be duplicate PQ.push(u, d[u])
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)\) |