Dijkstra's Algorithm Single Source Shortest Path in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find shortest paths grows as the graph gets bigger.
How does the number of steps change when we have more nodes and edges?
Analyze the time complexity of the following code snippet.
fn dijkstra(graph: &Vec>, start: usize) -> Vec {
let n = graph.len();
let mut dist = vec![usize::MAX; n];
let mut heap = std::collections::BinaryHeap::new();
dist[start] = 0;
heap.push(std::cmp::Reverse((0, start)));
while let Some(std::cmp::Reverse((cost, u))) = heap.pop() {
if dist[u] < cost { continue; }
for &(v, w) in &graph[u] {
let next = cost + w;
if next < dist[v] {
dist[v] = next;
heap.push(std::cmp::Reverse((next, v)));
}
}
}
dist
}
This code finds the shortest path from one node to all others using a priority queue.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop runs as long as there are nodes in the priority queue.
- How many times: Each edge is checked at most once when relaxing distances.
As the graph grows, the number of edges and nodes increase, affecting how many times we update distances and push to the queue.
| Input Size (n nodes, m edges) | Approx. Operations |
|---|---|
| 10 nodes, 20 edges | About 20 edge checks and several queue operations |
| 100 nodes, 500 edges | About 500 edge checks and many queue operations |
| 1000 nodes, 5000 edges | About 5000 edge checks and many queue operations |
Pattern observation: The work grows roughly with the number of edges and nodes, increasing more than just linearly.
Time Complexity: O((n + m) log n)
This means the time grows with the number of nodes and edges, multiplied by the cost of managing the priority queue.
[X] Wrong: "Dijkstra's always runs in O(n²) time no matter what."
[OK] Correct: Using a priority queue reduces the time to about 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 a priority queue is smart.
"What if we used a simple array instead of a priority queue? 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 to all other points in a graph with non-negative edge weights.Step 2: Compare with other graph algorithms
Other options like maximum flow, cycle detection, and topological sorting serve different purposes unrelated to shortest paths.Final Answer:
To find the shortest path from a single source to all other vertices -> Option AQuick Check:
Dijkstra's Algorithm = shortest path from single source [OK]
- Confusing with algorithms for max flow or cycle detection
- Thinking it sorts vertices instead of finding paths
- Assuming it works with negative weights
Solution
Step 1: Identify the need for priority selection
Dijkstra's Algorithm requires repeatedly selecting the vertex with the smallest tentative distance efficiently.Step 2: Choose the appropriate Rust data structure
BinaryHeap is a priority queue in Rust that allows efficient extraction of the smallest or largest element, suitable for this purpose.Final Answer:
BinaryHeap -> Option CQuick Check:
Priority queue = BinaryHeap [OK]
- Using VecDeque which is a queue, not priority queue
- Using HashMap which stores key-value pairs but no priority
- Using LinkedList which is inefficient for priority access
let graph = vec![ vec![(1, 4), (2, 1)], vec![(3, 1)], vec![(1, 2), (3, 5)], vec![] ];Options show distances from vertex 0 to vertices 0,1,2,3.
Solution
Step 1: Initialize distances and process neighbors
Start at vertex 0 with distance 0. Neighbors: vertex 1 (4), vertex 2 (1). Distances: [0,4,1,ā]Step 2: Pick vertex with smallest distance and update
Pick vertex 2 (distance 1). Update vertex 1 via 2: 1 + 2 = 3 (less than 4), update vertex 3 via 2: 1 + 5 = 6. Distances: [0,3,1,6]Step 3: Pick vertex 1 (distance 3) and update
From vertex 1 to 3: 3 + 1 = 4 (less than 6), update vertex 3 distance to 4. Distances: [0,3,1,4]Final Answer:
[0, 3, 1, 4] -> Option BQuick Check:
Shortest distances from 0 = [0,3,1,4] [OK]
- Not updating vertex 1 distance via vertex 2
- Ignoring better path to vertex 3 through vertex 1
- Confusing edge weights or directions
let mut dist = vec![usize::MAX; graph.len()];
dist[source] = 0;
let mut heap = BinaryHeap::new();
heap.push((0, source));
while let Some((cost, u)) = heap.pop() {
if cost > dist[u] { continue; }
for &(v, w) in &graph[u] {
let next = cost + w;
if next < dist[v] {
dist[v] = next;
heap.push((next, v));
}
}
}
Solution
Step 1: Understand Rust's BinaryHeap behavior
Rust's BinaryHeap is a max-heap by default, but Dijkstra needs a min-heap to get the smallest cost first.Step 2: Fix heap to behave as min-heap
Use std::cmp::Reverse wrapper around cost to invert ordering, so smallest cost is popped first.Final Answer:
Change heap to store (Reverse<usize>, usize) to get min-heap behavior -> Option DQuick Check:
BinaryHeap is max-heap by default, use Reverse for min-heap [OK]
- Ignoring heap ordering and getting wrong shortest paths
- Initializing distances incorrectly
- Removing important cost check causing wrong updates
0 -> 1 (2), 0 -> 2 (4), 1 -> 2 (1), 1 -> 3 (7), 2 -> 4 (3), 3 -> 4 (1)What is the shortest path distance from vertex 0 to vertex 4 using Dijkstra's Algorithm?
Solution
Step 1: Calculate tentative distances from vertex 0
Start: dist[0]=0, others ā. From 0: dist[1]=2, dist[2]=4.Step 2: Update distances via vertex 1 and 2
From 1: dist[2]=min(4, 2+1=3)=3, dist[3]=2+7=9. From 2: dist[4]=3+3=6.Step 3: Update distance via vertex 3
From 3: dist[4]=min(6, 9+1=10)=6 (no change).Final Answer:
6 -> Option AQuick Check:
Shortest distance 0->4 = 6 [OK]
- Not updating dist[2] via vertex 1 to 3
- Ignoring shorter path through vertex 2
- Adding edges incorrectly or missing updates
Ready to prove it?
Pass the test and this pattern moves unknown
