Connected Components Using BFS in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find connected parts in a graph grows as the graph gets bigger.
Specifically, how the BFS method explores nodes and edges affects the total work done.
Analyze the time complexity of the following code snippet.
fn connected_components_bfs(graph: &Vec>) -> usize {
let mut visited = vec![false; graph.len()];
let mut count = 0;
for i in 0..graph.len() {
if !visited[i] {
let mut queue = std::collections::VecDeque::new();
queue.push_back(i);
visited[i] = true;
while let Some(node) = queue.pop_front() {
for &neighbor in &graph[node] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
count += 1;
}
}
count
}
This code counts how many separate groups of connected nodes exist in the graph using BFS.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The BFS loop that visits each node and its neighbors once.
- How many times: Each node is visited once, and each edge is checked once during BFS.
As the graph grows with more nodes and edges, the BFS explores all nodes and edges 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 work grows roughly in proportion to the number of nodes plus edges.
Time Complexity: O(n + m)
This means the time grows linearly with the total number of nodes and edges in the graph.
[X] Wrong: "The BFS runs in O(n²) because it has nested loops."
[OK] Correct: Although there are loops inside loops, each edge and node is visited only once, so the total work is linear, not quadratic.
Understanding BFS time complexity helps you explain how graph traversal scales and why it is efficient for connected components.
"What if the graph was represented as an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand BFS in context of connected components
BFS explores all nodes reachable from a starting node, grouping them as one connected component.Step 2: Differentiate BFS purpose from other graph properties
While BFS can find shortest paths, here it is used to find connected nodes, not just paths or degrees.Final Answer:
All nodes reachable from a starting node forming one connected component -> Option AQuick Check:
BFS finds connected components = A [OK]
- Confusing BFS with shortest path only
- Thinking BFS counts edges or degrees
- Assuming BFS finds all components at once
Solution
Step 1: Identify correct queue type for BFS in Rust
BFS requires a queue with efficient push/pop from front; VecDeque supports this.Step 2: Check other options for correctness
Vec is a stack-like structure, HashMap is a key-value store, LinkedList is less common and less efficient for BFS.Final Answer:
let mut queue: VecDeque<usize> = VecDeque::new(); -> Option AQuick Check:
Use VecDeque for BFS queue in Rust = C [OK]
- Using Vec which is inefficient for queue operations
- Using HashMap instead of a queue
- Confusing LinkedList with VecDeque
use std::collections::{VecDeque, HashMap};
fn bfs(start: usize, graph: &HashMap, visited: &mut Vec<bool>) -> Vec<usize> {
let mut queue = VecDeque::new();
let mut component = Vec::new();
queue.push_back(start);
visited[start] = true;
while let Some(node) = queue.pop_front() {
component.push(node);
if let Some(neighbors) = graph.get(&node) {
for &neighbor in neighbors {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
}
component
}
fn main() {
let mut graph = HashMap::new();
graph.insert(0, vec![1]);
graph.insert(1, vec![0, 2]);
graph.insert(2, vec![1]);
graph.insert(3, vec![4]);
graph.insert(4, vec![3]);
let mut visited = vec![false; 5];
let comp1 = bfs(0, &graph, &mut visited);
let comp2 = bfs(3, &graph, &mut visited);
println!("Component 1: {:?}", comp1);
println!("Component 2: {:?}", comp2);
}Solution
Step 1: Trace BFS from node 0
Starting at 0, BFS visits 0, then 1, then 2. So component 1 is [0, 1, 2].Step 2: Trace BFS from node 3
Starting at 3, BFS visits 3, then 4. So component 2 is [3, 4].Final Answer:
Component 1: [0, 1, 2] Component 2: [3, 4] -> Option DQuick Check:
BFS groups connected nodes correctly = D [OK]
- Mixing nodes between components
- Ignoring visited array updates
- Assuming BFS order is sorted
fn bfs(start: usize, graph: &HashMap, visited: &mut Vec<bool>) -> Vec<usize> {
let mut queue = VecDeque::new();
let mut component = Vec::new();
queue.push_back(start);
// visited[start] = true; // Missing this line
while let Some(node) = queue.pop_front() {
component.push(node);
if let Some(neighbors) = graph.get(&node) {
for &neighbor in neighbors {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
}
component
}Solution
Step 1: Check visited marking for start node
The line marking start node as visited is commented out, so BFS may revisit it causing infinite loop or wrong results.Step 2: Verify other parts of code
Queue is initialized, neighbors checked, and component returned correctly.Final Answer:
The starting node is not marked visited before BFS loop -> Option CQuick Check:
Mark start visited before BFS = A [OK]
- Forgetting to mark start visited
- Not initializing queue
- Ignoring neighbor existence check
Solution
Step 1: Identify connected groups from edges
Edges connect 0-1-2 forming one component, 3-4 forming second, and node 5 is isolated alone.Step 2: Count components and list nodes
There are 3 components: [0,1,2], [3,4], and [5].Final Answer:
3 components: [0,1,2], [3,4], [5] (isolated node) -> Option BQuick Check:
Count isolated and connected groups correctly = B [OK]
- Grouping isolated node with others
- Merging disconnected components
- Miscounting total components
Ready to prove it?
Pass the test and this pattern moves unknown
