Dijkstra's Algorithm
Dijkstra's algorithm is a fundamental graph algorithm that finds the shortest paths from a source vertex to all other vertices in a weighted graph with non-negative edge weights. It uses a greedy approach with a priority queue to efficiently compute optimal paths.
Interactive Visualization
Node States
Drag nodes to reposition • Click to reset • 5 nodes, 7 edges
Dijkstra's Algorithm
Dijkstra's algorithm finds the shortest path between nodes in a weighted graph. It works by maintaining a set of unvisited nodes and continuously selecting the node with the smallest distance.
Time Complexity: O((V + E) log V) with priority queue
Space Complexity: O(V)
- Guarantees shortest path in weighted graphs
- Uses a greedy approach with priority queue
- Works with non-negative edge weights
- Optimal for single-source shortest path problems
Greedy Shortest Path Strategy
Dijkstra's algorithm employs a greedy strategy that always selects the unvisited vertex with the minimum distance from the source. This locally optimal choice leads to a globally optimal solution for finding shortest paths in graphs with non-negative edge weights.
1. Initialize
Set distance to source as 0, all others as infinity; use priority queue
2. Extract Minimum
Remove vertex with minimum distance from priority queue
3. Relax Edges
Update distances to neighbors if shorter path is found
Key Insight
The greedy choice works because once a vertex is processed (removed from the priority queue), we have found the shortest path to it. This is guaranteed by the non-negative weight constraint - any future path through other vertices cannot be shorter.
How Dijkstra's Algorithm Works
Step-by-Step Process
Edge Relaxation Process
Current Distance: distance[u] (shortest known distance to current vertex u)
Edge Weight: weight(u, v) (weight of edge from u to neighbor v)
New Distance: distance[u] + weight(u, v)
Update Condition: If new distance < distance[v], update distance[v]
Priority Queue: Add/update v in priority queue with new distance
Example: Finding Shortest Paths from A
Pseudocode
ALGORITHM Dijkstra(graph, source)
BEGIN
// Initialize distances and priority queue
FOR each vertex v IN graph DO
distance[v] = INFINITY
previous[v] = UNDEFINED
END FOR
distance[source] = 0
priorityQueue = empty priority queue
visited = empty set
// Add source to priority queue
priorityQueue.insert(source, 0)
// Main algorithm loop
WHILE priorityQueue is not empty DO
// Extract vertex with minimum distance
u = priorityQueue.extractMin()
// Skip if already processed
IF u IN visited THEN
CONTINUE
END IF
// Mark as visited
visited.add(u)
// Process all neighbors of u
FOR each neighbor v OF u DO
IF v IN visited THEN
CONTINUE
END IF
// Calculate new distance through u
newDistance = distance[u] + weight(u, v)
// Relax edge if shorter path found
IF newDistance < distance[v] THEN
distance[v] = newDistance
previous[v] = u
priorityQueue.insert(v, newDistance)
END IF
END FOR
END WHILE
RETURN distance, previous
END
// Reconstruct shortest path from source to target
ALGORITHM GetPath(previous, source, target)
BEGIN
path = empty list
current = target
WHILE current is not UNDEFINED DO
path.prepend(current)
current = previous[current]
END WHILE
IF path[0] != source THEN
RETURN empty list // No path exists
END IF
RETURN path
END Complexity Analysis
| Implementation | Time Complexity | Space Complexity | Explanation |
|---|---|---|---|
| Binary Heap | O((V + E) log V) | O(V) | Standard implementation with priority queue |
| Fibonacci Heap | O(E + V log V) | O(V) | Theoretical optimum with amortized analysis |
| Simple Array | O(V²) | O(V) | Linear search for minimum distance vertex |
Why O((V + E) log V)?
- • Vertex Processing: Each vertex is extracted from priority queue once: O(V log V)
- • Edge Relaxation: Each edge is relaxed at most once: O(E log V)
- • Priority Queue Operations: Insert/decrease-key in O(log V) time
- • Total Operations: O(V log V + E log V) = O((V + E) log V)
Space Complexity Breakdown
- • Distance Array: O(V) space to store shortest distances
- • Previous Array: O(V) space for path reconstruction
- • Priority Queue: O(V) space for unprocessed vertices
- • Visited Set: O(V) space to track processed vertices
Critical Requirement: Non-negative Weights
Dijkstra's algorithm only works with non-negative edge weights. Negative weights violate the greedy property and can lead to incorrect results.
• Greedy choice becomes invalid
• Already processed vertices might need updates
• Algorithm terminates with wrong distances
• Bellman-Ford algorithm
• Floyd-Warshall for all-pairs
• Johnson's algorithm for sparse graphs
Code Examples
Here are implementations of Dijkstra's algorithm in different programming languages. Each version uses a priority queue for efficiency and includes both single-source shortest paths and point-to-point pathfinding with early termination.
import heapq
from collections import defaultdict
class Graph:
def __init__(self):
self.vertices = defaultdict(list)
def add_edge(self, from_vertex, to_vertex, weight):
"""Add weighted edge to the graph"""
self.vertices[from_vertex].append((to_vertex, weight))
def dijkstra(self, start, num_vertices):
"""
Find shortest paths from start vertex to all other vertices
Time Complexity: O((V + E) log V)
Space Complexity: O(V)
"""
# Initialize distances to infinity
distances = [float('inf')] * num_vertices
distances[start] = 0
# Track previous vertices for path reconstruction
previous = [-1] * num_vertices
# Priority queue: (distance, vertex)
pq = [(0, start)]
visited = set()
while pq:
current_distance, current_vertex = heapq.heappop(pq)
# Skip if already processed
if current_vertex in visited:
continue
visited.add(current_vertex)
# Process all neighbors
for neighbor, weight in self.vertices[current_vertex]:
if neighbor in visited:
continue
# Calculate new distance through current vertex
new_distance = current_distance + weight
# If we found a shorter path, update it
if new_distance < distances[neighbor]:
distances[neighbor] = new_distance
previous[neighbor] = current_vertex
heapq.heappush(pq, (new_distance, neighbor))
return distances, previous
def dijkstra_with_target(self, start, target, num_vertices):
"""Find shortest path between two specific vertices"""
distances = [float('inf')] * num_vertices
distances[start] = 0
previous = [-1] * num_vertices
pq = [(0, start)]
visited = set()
while pq:
current_distance, current_vertex = heapq.heappop(pq)
if current_vertex in visited:
continue
visited.add(current_vertex)
# Early termination if we reached the target
if current_vertex == target:
break
for neighbor, weight in self.vertices[current_vertex]:
if neighbor in visited:
continue
new_distance = current_distance + weight
if new_distance < distances[neighbor]:
distances[neighbor] = new_distance
previous[neighbor] = current_vertex
heapq.heappush(pq, (new_distance, neighbor))
# Reconstruct path
path = []
current = target
while current != -1:
path.append(current)
current = previous[current]
path.reverse()
# Check if path is valid
if not path or path[0] != start:
path = [] # No path exists
return distances[target], path
# Example usage
if __name__ == "__main__":
# Create graph: A=0, B=1, C=2, D=3, E=4, F=5
graph = Graph()
num_vertices = 6
# Add edges (bidirectional)
edges = [
(0, 1, 4), (0, 3, 2), # A-B, A-D
(1, 0, 4), (1, 2, 3), (1, 4, 1), # B-A, B-C, B-E
(2, 1, 3), (2, 4, 2), (2, 5, 1), # C-B, C-E, C-F
(3, 0, 2), (3, 4, 5), # D-A, D-E
(4, 1, 1), (4, 2, 2), (4, 3, 5), (4, 5, 3), # E-B, E-C, E-D, E-F
(5, 2, 1), (5, 4, 3) # F-C, F-E
]
for from_v, to_v, weight in edges:
graph.add_edge(from_v, to_v, weight)
start = 0 # Node A
distances, previous = graph.dijkstra(start, num_vertices)
print(f"Shortest distances from vertex {start}:")
node_names = ['A', 'B', 'C', 'D', 'E', 'F']
for i in range(num_vertices):
distance = "∞" if distances[i] == float('inf') else distances[i]
print(f" To {node_names[i]}: {distance}")
# Find shortest path to specific vertex
target = 5 # Node F
distance, path = graph.dijkstra_with_target(start, target, num_vertices)
print(f"\nShortest path from {node_names[start]} to {node_names[target]}:")
path_names = [node_names[node] for node in path]
print(f" Path: {' -> '.join(path_names)}")
print(f" Distance: {distance}") Practical Applications
Perfect For
- • GPS Navigation: Finding shortest routes in road networks
- • Network Routing: Optimal packet routing in computer networks
- • Social Networks: Finding shortest connection paths between users
- • Flight Planning: Shortest travel times between airports
- • Game Pathfinding: AI navigation with weighted terrain costs
- • Resource Allocation: Minimum cost flow problems
Consider Alternatives When
- • Negative Weights: Use Bellman-Ford or Johnson's algorithm
- • All-Pairs Shortest Paths: Floyd-Warshall may be more suitable
- • Unweighted Graphs: Simple BFS is faster and simpler
- • Very Dense Graphs: Consider O(V²) implementation
- • Dynamic Graphs: Frequent edge updates make preprocessing costly