What Is Dijkstra's Algorithm?
Dijkstra's algorithm finds the shortest path from a single source vertex to all other vertices in a weighted graph with non-negative edge weights.
Published by Edsger Dijkstra in 1959, it is one of the most important algorithms in computer science — it powers Google Maps, GPS navigation, network routing protocols (OSPF), and countless other applications.
Core idea: Always process the unvisited vertex with the smallest current known distance. This greedy choice guarantees optimality for non-negative weights.
Complete Implementation
import heapq
from collections import defaultdict
def dijkstra(graph, source, n):
"""
Dijkstra's shortest path algorithm.
graph: dict {u: [(v, weight), ...]}
source: starting vertex
n: number of vertices
Time: O((V + E) log V) with binary heap
Space: O(V + E)
"""
# Initialize distances
dist = [float('inf')] * n
dist[source] = 0
# Min-heap: (distance, vertex)
priority_queue = [(0, source)]
while priority_queue:
d, u = heapq.heappop(priority_queue)
# Skip outdated entries (lazy deletion)
if d > dist[u]:
continue
# Process each neighbor
for v, weight in graph[u]:
new_dist = dist[u] + weight
if new_dist < dist[v]:
dist[v] = new_dist
heapq.heappush(priority_queue, (new_dist, v))
return dist
# Build graph
graph = defaultdict(list)
edges = [(0,1,4),(0,2,1),(2,1,2),(1,3,1),(2,3,5),(3,4,3)]
for u, v, w in edges:
graph[u].append((v, w))
graph[v].append((u, w))
dist = dijkstra(dict(graph), 0, 5)
print(dist) # [0, 3, 1, 4, 7]
# Vertex 1: 0→2→1 = 1+2 = 3 (shorter than 0→1 = 4)
Step-by-Step Trace
| Step 1 | Extract (0, vertex=0) |
| Neighbor 1: dist[0]+4=4 < ∞ | dist[1]=4, PQ add (4,1) |
| Neighbor 2: dist[0]+1=1 < ∞ | dist[2]=1, PQ add (1,2) |
| Step 2 | Extract (1, vertex=2) |
| Neighbor 0: dist[2]+1=2 > dist[0]=0 | skip |
| Neighbor 1: dist[2]+2=3 < dist[1]=4 | dist[1]=3, PQ add (3,1) |
| Neighbor 3: dist[2]+5=6 < ∞ | dist[3]=6, PQ add (6,3) |
| Step 3 | Extract (3, vertex=1) |
| Neighbor 3: dist[1]+1=4 < dist[3]=6 | dist[3]=4, PQ add (4,3) |
| Step 4 | Extract (4, vertex=1) — SKIP! dist[1]=3 ≠ 4 (outdated) |
| Step 5 | Extract (4, vertex=3) |
| Neighbor 4: dist[3]+3=7 < ∞ | dist[4]=7, PQ add (7,4) |
| Step 6 | Extract (6, vertex=3) — SKIP! dist[3]=4 ≠ 6 (outdated) |
| Step 7 | Extract (7, vertex=4) |
| Final | dist = [0, 3, 1, 4, 7] ✓ |
Path Reconstruction
def dijkstra_with_path(graph, source, target, n):
"""
Find shortest path AND the actual route from source to target.
"""
dist = [float('inf')] * n
dist[source] = 0
parent = [-1] * n # Track previous vertex in shortest path
pq = [(0, source)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, weight in graph[u]:
new_dist = dist[u] + weight
if new_dist < dist[v]:
dist[v] = new_dist
parent[v] = u # Remember we came from u
heapq.heappush(pq, (new_dist, v))
# Reconstruct path by following parent pointers backward
path = []
current = target
while current != -1:
path.append(current)
current = parent[current]
path.reverse()
if path[0] == source:
return dist[target], path
return float('inf'), [] # Target not reachable
graph = defaultdict(list)
for u, v, w in [(0,1,4),(0,2,1),(2,1,2),(1,3,1),(2,3,5),(3,4,3)]:
graph[u].append((v, w)); graph[v].append((u, w))
dist, path = dijkstra_with_path(dict(graph), 0, 4, 5)
print(f"Shortest distance: {dist}") # 7
print(f"Path: {path}") # [0, 2, 1, 3, 4]
Dijkstra with Named Vertices
def dijkstra_named(graph, source):
"""Dijkstra for graphs with string/named vertices."""
dist = {v: float('inf') for v in graph}
dist[source] = 0
pq = [(0, source)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]: continue
for v, w in graph.get(u, []):
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
city_graph = {
'Delhi': [('Mumbai', 1400), ('Jaipur', 300)],
'Mumbai': [('Delhi', 1400), ('Pune', 150)],
'Jaipur': [('Delhi', 300), ('Ahmedabad', 250)],
'Pune': [('Mumbai', 150)],
'Ahmedabad': [('Jaipur', 250)]
}
print(dijkstra_named(city_graph, 'Delhi'))
# {'Delhi':0, 'Mumbai':1400, 'Jaipur':300, 'Pune':1550, 'Ahmedabad':550}
Why Dijkstra Fails with Negative Weights
| Graph: A | B (-5), A→C (3), C→B (2) |
| Process A | dist[B]=−5, dist[C]=3 |
| Extract C (dist=3) | dist[B] = 3+2=5 but we already finalized B at −5! |
| The problem | Dijkstra's greedy assumption is that once a vertex is extracted |
| For negative weights | use Bellman-Ford instead. |
Time Complexity with Different Data Structures
| Priority Queue | Extract Min | Decrease Key | Total Complexity |
|---|
| Binary Heap (heapq) | O(log V) | O(log V) via lazy | O((V + E) log V) |
| Array (naive) | O(V) | O(1) | O(V²) |
| Fibonacci Heap | O(log V) amortized | O(1) amortized | O(E + V log V) |
Practical recommendation:
- Use binary heap (Python's
heapq) — O((V+E) log V) for most problems - For very dense graphs (E ≈ V²), array-based O(V²) may be comparable
- Fibonacci heap gives theoretical optimum but is complex to implement
Optimization — Early Termination
If you only need the shortest path to ONE specific target:
def dijkstra_to_target(graph, source, target, n):
"""Stop as soon as target is extracted from PQ."""
dist = [float('inf')] * n
dist[source] = 0
pq = [(0, source)]
while pq:
d, u = heapq.heappop(pq)
if u == target:
return d # ← Early exit: target found with optimal distance
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return float('inf')
Dijkstra Variants
Modified Dijkstra for Maximum Capacity Path
def max_capacity_path(graph, source, target, n):
"""
Find path that maximizes the minimum edge weight.
(bottleneck shortest path — used in network flow)
Change: maximize instead of minimize, use max instead of +
"""
cap = [0] * n
cap[source] = float('inf')
pq = [(-float('inf'), source)] # Negate for max-heap behavior
while pq:
neg_c, u = heapq.heappop(pq)
c = -neg_c
if c < cap[u]: continue
if u == target: return cap[target]
for v, w in graph[u]:
new_cap = min(c, w)
if new_cap > cap[v]:
cap[v] = new_cap
heapq.heappush(pq, (-new_cap, v))
return cap[target]
Summary
Dijkstra's algorithm:
- Finds shortest path from one source to all vertices
- Uses a min-priority queue (greedy: always process closest unfinished vertex)
- Time: O((V+E) log V) with binary heap
- Requires: Non-negative edge weights
- Path reconstruction: maintain parent array alongside distance array
Used in: GPS navigation, network routing (OSPF), game AI pathfinding, traffic management
*Next Lesson: Bellman-Ford Algorithm*