BFS Breadth First Search on Graph in DSA Rust - Time & Space Complexity
We want to understand how the time needed to explore a graph grows as the graph gets bigger.
How does BFS handle more nodes and edges in the graph?
Analyze the time complexity of the following code snippet.
fn bfs(graph: &Vec, start: usize) {
let mut visited = vec![false; graph.len()];
let mut queue = std::collections::VecDeque::new();
visited[start] = true;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
for &neighbor in &graph[node] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
}
This code visits all nodes reachable from the start node using BFS, exploring neighbors level by level.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop runs as long as there are nodes in the queue, and inside it, the for loop visits all neighbors of the current node.
- How many times: Each node is enqueued and dequeued once, and each edge is checked once during neighbor visits.
As the graph grows, BFS visits every node and edge once.
| Input Size (n nodes, m edges) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 10 node visits + 15 edge checks = 25 operations |
| 100 nodes, 200 edges | About 100 node visits + 200 edge checks = 300 operations |
| 1000 nodes, 5000 edges | About 1000 node visits + 5000 edge checks = 6000 operations |
Pattern observation: The total work grows roughly in proportion to the number of nodes plus edges.
Time Complexity: O(n + m)
This means BFS takes time proportional to the total number of nodes plus edges in the graph.
[X] Wrong: "BFS always takes O(n²) time because it uses nested loops."
[OK] Correct: BFS visits each node and edge only once, so it does not repeatedly check all pairs. The loops work together to cover all nodes and edges efficiently.
Understanding BFS time complexity helps you explain how graph searches scale and why BFS is efficient for many problems involving shortest paths or connectivity.
"What if the graph is represented as an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand BFS traversal method
BFS explores nodes level by level, visiting all neighbors before moving deeper.Step 2: Identify the data structure used
To achieve level order traversal, BFS uses a queue to process nodes in the order they are discovered.Final Answer:
A queue data structure -> Option DQuick Check:
BFS uses queue = C [OK]
- Confusing BFS with DFS which uses stack
- Thinking BFS uses priority queue
- Assuming BFS uses hash map for traversal order
Solution
Step 1: Identify the correct queue type in Rust
Rust's standard library provides VecDeque for efficient queue operations (push_back, pop_front).Step 2: Check each option
let mut queue = VecDeque::new(); uses VecDeque::new(), which is correct. Vec is a dynamic array, not ideal for queue. HashMap and BinaryHeap are not queues.Final Answer:
let mut queue = VecDeque::new(); -> Option CQuick Check:
VecDeque is queue in Rust [OK]
- Using Vec which is not efficient for queue
- Using HashMap which is for key-value pairs
- Using BinaryHeap which is a priority queue
use std::collections::VecDeque;
fn bfs(graph: &Vec, start: usize) -> Vec<usize> {
let mut visited = vec![false; graph.len()];
let mut queue = VecDeque::new();
let mut order = Vec::new();
visited[start] = true;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
order.push(node);
for &neighbor in &graph[node] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
order
}
fn main() {
let graph = vec![
vec![1, 2],
vec![0, 3],
vec![0, 4],
vec![1],
vec![2]
];
let result = bfs(&graph, 0);
println!("{:?}", result);
}Solution
Step 1: Start BFS from node 0
Visit node 0, enqueue neighbors 1 and 2.Step 2: Visit nodes in queue order
Dequeue 1, visit it, enqueue neighbor 3 (0 already visited). Dequeue 2, visit it, enqueue neighbor 4.Step 3: Continue visiting remaining nodes
Dequeue 3, visit it (neighbor 1 visited). Dequeue 4, visit it (neighbor 2 visited).Final Answer:
[0, 1, 2, 3, 4] -> Option AQuick Check:
BFS order from 0 = [0,1,2,3,4] [OK]
- Mixing BFS with DFS order
- Visiting neighbors out of order
- Forgetting to mark visited nodes
use std::collections::VecDeque;
fn bfs(graph: &Vec, start: usize) -> Vec<usize> {
let mut visited = vec![false; graph.len()];
let mut queue = VecDeque::new();
let mut order = Vec::new();
visited[start] = true;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
order.push(node);
for &neighbor in &graph[node] {
if !visited[neighbor] {
queue.push_back(neighbor);
}
}
}
order
}
fn main() {
let graph = vec![
vec![1, 2],
vec![0, 3],
vec![0, 4],
vec![1],
vec![2]
];
let result = bfs(&graph, 0);
println!("{:?}", result);
}Solution
Step 1: Analyze BFS loop and visited marking
The code pushes neighbors to queue but never marks them visited.Step 2: Understand consequence of missing visited update
Without marking visited, neighbors get re-added repeatedly causing infinite loop or panic.Final Answer:
Missing visited[neighbor] = true; causes infinite loop -> Option AQuick Check:
Mark visited before enqueue = prevent infinite loop [OK]
- Forgetting to mark nodes visited before enqueue
- Assuming queue initialization causes panic
- Ignoring start node validity
use std::collections::VecDeque;
fn shortest_path_length(graph: &Vec, start: usize, target: usize) -> Option<usize> {
let mut visited = vec![false; graph.len()];
let mut distance = vec![0; graph.len()];
let mut queue = VecDeque::new();
visited[start] = true;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
if node == target {
return Some(distance[node]);
}
for &neighbor in &graph[node] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
// What to add here?
}
}
}
None
}
fn main() {
let graph = vec![
vec![1, 2],
vec![0, 3],
vec![0, 4],
vec![1],
vec![2]
];
let result = shortest_path_length(&graph, 0, 4);
println!("{:?}", result);
}Solution
Step 1: Understand BFS distance tracking
Distance to neighbor is distance to current node plus one edge.Step 2: Choose correct distance update
Assign distance[neighbor] = distance[node] + 1 to track shortest path length.Final Answer:
distance[neighbor] = distance[node] + 1; -> Option BQuick Check:
Distance increments by 1 per edge [OK]
- Not updating distance correctly
- Using += 1 without initialization
- Setting distance to constant 1 always
Ready to prove it?
Pass the test and this pattern moves unknown
