Dijkstra's Algorithm Single Source Shortest Path in DSA Go - Time & Space Complexity
We want to understand how the time needed to find shortest paths grows as the graph gets bigger.
How does the algorithm's work increase when there are more nodes and edges?
Analyze the time complexity of the following Dijkstra's algorithm code snippet.
func Dijkstra(graph map[int]map[int]int, start int) map[int]int {
dist := make(map[int]int)
for node := range graph {
dist[node] = 1<<31 - 1 // infinity
}
dist[start] = 0
pq := &PriorityQueue{}
heap.Init(pq)
heap.Push(pq, &Item{node: start, priority: 0})
for pq.Len() > 0 {
current := heap.Pop(pq).(*Item)
for neighbor, weight := range graph[current.node] {
newDist := dist[current.node] + weight
if newDist < dist[neighbor] {
dist[neighbor] = newDist
heap.Push(pq, &Item{node: neighbor, priority: newDist})
}
}
}
return dist
}
This code finds the shortest distance from one start node to all others using a priority queue.
Look at what repeats as the graph size grows.
- Primary operation: Extracting the node with smallest distance from the priority queue and relaxing edges.
- How many times: Each node is extracted once, and each edge is checked when relaxing neighbors.
As the graph grows, the number of nodes and edges increases, affecting the work done.
| Input Size (n nodes, m edges) | Approx. Operations |
|---|---|
| 10 nodes, 20 edges | About 10 extractions + 20 edge checks |
| 100 nodes, 500 edges | About 100 extractions + 500 edge checks |
| 1000 nodes, 5000 edges | About 1000 extractions + 5000 edge checks |
Pattern observation: The work grows roughly with the number of nodes plus edges; more edges mean more checks.
Time Complexity: O((n + m) log n)
This means the time grows with nodes and edges, multiplied by the cost of managing the priority queue.
[X] Wrong: "Dijkstra's algorithm always runs in O(n²) time regardless of data structures."
[OK] Correct: Using a priority queue reduces the time to O((n + m) log n), which is faster especially for sparse graphs.
Understanding this time complexity helps you explain how your solution scales and why using the right data structure matters.
"What if we replaced the priority queue with a simple array? How would the time complexity change?"
Practice
Solution
Step 1: Understand the algorithm's goal
Dijkstra's Algorithm is designed to find the shortest paths from one starting point (source) to all other points in a graph with non-negative edge weights.Step 2: Compare with other graph algorithms
Other algorithms like max flow or cycle detection serve different purposes, so they do not match Dijkstra's goal.Final Answer:
To find the shortest path from a single source to all other vertices -> Option CQuick Check:
Dijkstra's Algorithm = shortest path from source [OK]
- Confusing Dijkstra with max flow algorithms
- Thinking it detects cycles
- Assuming it sorts vertices
Solution
Step 1: Identify priority queue requirement
Dijkstra's Algorithm requires a priority queue to efficiently get the vertex with the smallest tentative distance.Step 2: Choose Go's best priority queue
Go's standard library provides the container/heap package which supports heap operations. Using IntHeap or a custom heap is the best choice for priority queue.Final Answer:
container/heap package's IntHeap implementation -> Option AQuick Check:
Priority queue in Go = container/heap [OK]
- Using slice without heap operations (slow)
- Using map for priority queue (wrong type)
- Using linked list for priority queue
dist := []int{1e9, 1e9, 1e9}
dist[0] = 0
pq := &IntHeap{{0, 0}} // {distance, vertex}
heap.Init(pq)
for pq.Len() > 0 {
cur := heap.Pop(pq).(Pair)
d, u := cur.distance, cur.vertex
if d > dist[u] { continue }
for _, e := range graph[u] {
v, w := e.vertex, e.weight
if dist[u]+w < dist[v] {
dist[v] = dist[u] + w
heap.Push(pq, Pair{dist[v], v})
}
}
}
fmt.Println(dist)Solution
Step 1: Initialize distances and priority queue
Distances start as [inf, inf, inf], then dist[0] = 0. Priority queue starts with vertex 0 at distance 0.Step 2: Relax edges step-by-step
From 0: dist[1] = min(inf, 0+4)=4, dist[2] = min(inf, 0+1)=1. Push (4,1) and (1,2). Next pop (1,2): from 2 to 1: dist[1] = min(4,1+2)=3. Update dist[1] to 3. Final distances: [0,3,1].Final Answer:
[0 3 1] -> Option BQuick Check:
Shortest distances = [0 3 1] [OK]
- Not updating dist[1] after checking edge 2->1
- Mixing up vertex indices
- Ignoring priority queue order
dist := make([]int, n)
for i := range dist {
dist[i] = 1e9
}
dist[src] = 0
pq := &IntHeap{{0, src}}
heap.Init(pq)
for pq.Len() > 0 {
cur := heap.Pop(pq).(Pair)
d, u := cur.distance, cur.vertex
if d < dist[u] { continue }
for _, e := range graph[u] {
v, w := e.vertex, e.weight
if dist[u]+w < dist[v] {
dist[v] = dist[u] + w
heap.Push(pq, Pair{dist[v], v})
}
}
}Solution
Step 1: Check the distance comparison condition
The condition 'if d < dist[u]' is incorrect because we want to skip processing if the current distance is greater than the stored distance.Step 2: Correct condition for skipping outdated entries
The correct condition is 'if d > dist[u]' to ignore stale entries in the priority queue.Final Answer:
The condition should be 'if d > dist[u]' instead of 'if d < dist[u]' -> Option DQuick Check:
Skip outdated if d > dist[u] [OK]
- Using wrong comparison operator in distance check
- Not initializing dist with large values
- Forgetting to initialize priority queue
Solution
Step 1: Initialize distances and start from vertex 0
distances: [0, inf, inf, inf, inf]. From 0: dist[1]=2, dist[2]=4.Step 2: Relax edges from vertex 1 and 2
From 1: dist[2]=min(4,2+1)=3, dist[3]=2+7=9. From 2: dist[4]=3+3=6.Step 3: Relax edges from vertex 3
From 3: dist[4]=min(6,9+1=10)=6 (no change).Final Answer:
6 -> Option AQuick Check:
Shortest path 0->1->2->4 = 6 [OK]
- Ignoring shorter path through vertex 2
- Adding edges incorrectly
- Not updating distances after relaxation
Ready to prove it?
Pass the test and this pattern moves unknown
