DFS Depth First Search on Graph in DSA Rust - Time & Space Complexity
We want to understand how the time taken by Depth First Search (DFS) grows as the graph size increases.
Specifically, how does DFS's work change when the graph has more nodes and edges?
Analyze the time complexity of the following code snippet.
fn dfs(graph: &Vec<Vec<usize>>, node: usize, visited: &mut Vec<bool>) {
visited[node] = true;
for &neighbor in &graph[node] {
if !visited[neighbor] {
dfs(graph, neighbor, visited);
}
}
}
This code visits all nodes reachable from a starting node using DFS on an adjacency list graph.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Visiting each node and exploring its neighbors recursively.
- How many times: Each node is visited once, and each edge is checked once.
As the number of nodes (n) and edges (e) grow, DFS visits all nodes and edges once.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 10 node visits + 15 edge checks = 25 operations |
| 100 nodes, 200 edges | About 100 + 200 = 300 operations |
| 1000 nodes, 5000 edges | About 1000 + 5000 = 6000 operations |
Pattern observation: Operations grow roughly in proportion to nodes plus edges.
Time Complexity: O(n + e)
This means DFS takes time proportional to the number of nodes plus edges in the graph.
[X] Wrong: "DFS always takes O(n²) time because it uses recursion and loops."
[OK] Correct: DFS visits each node and edge only once, so its time depends on the graph size, not the square of nodes.
Understanding DFS time complexity helps you explain how graph traversal scales, a key skill in many coding challenges.
"What if the graph was represented as an adjacency matrix instead of a list? How would the time complexity change?"
Practice
Depth First Search (DFS) on a graph?Solution
Step 1: Understand DFS traversal
DFS explores a graph by going deep into one branch before exploring others.Step 2: Compare with other traversal methods
BFS visits nodes level by level, but DFS goes deep first.Final Answer:
Explore as far as possible along each branch before backtracking -> Option AQuick Check:
DFS = deep exploration first [OK]
- Confusing DFS with BFS
- Thinking DFS visits nodes level-wise
- Assuming DFS visits nodes randomly
Solution
Step 1: Understand parameter passing in Rust for DFS
The graph should be passed by reference to avoid copying, and visited vector should be mutable reference to mark nodes visited.Step 2: Check each option
fn dfs(graph: &Vec, node: usize, visited: &mut Vec<bool>) passes graph by reference and visited as mutable reference, which is correct for DFS recursion.Final Answer:
fn dfs(graph: &Vec, node: usize, visited: &mut Vec<bool>) -> Option CQuick Check:
Graph by ref + visited mutable ref = correct DFS signature [OK]
- Passing graph or visited by value causing copies
- Not using mutable reference for visited
- Using mutable reference for graph unnecessarily
fn dfs(graph: &Vec, node: usize, visited: &mut Vec<bool>, result: &mut Vec<usize>) {
visited[node] = true;
result.push(node);
for &neighbor in &graph[node] {
if !visited[neighbor] {
dfs(graph, neighbor, visited, result);
}
}
}
fn main() {
let graph = vec![
vec![1, 2],
vec![0, 3],
vec![0],
vec![1]
];
let mut visited = vec![false; 4];
let mut result = Vec::new();
dfs(&graph, 0, &mut visited, &mut result);
println!("{:?}", result);
}Solution
Step 1: Trace DFS starting at node 0
Start at 0, mark visited, add 0. Neighbors: 1, 2. Visit 1 first.Step 2: Continue DFS from node 1
Visit 1, add 1. Neighbors: 0 (visited), 3 (not visited). Visit 3 next.Step 3: Visit node 3
Visit 3, add 3. Neighbor: 1 (visited). Backtrack to 0 and visit 2.Step 4: Visit node 2
Visit 2, add 2. Neighbor: 0 (visited). DFS complete.Final Answer:
[0, 1, 3, 2] -> Option BQuick Check:
DFS order from 0 = [0,1,3,2] [OK]
- Visiting neighbors out of order
- Not marking visited nodes
- Confusing BFS order with DFS
fn dfs(graph: &Vec, node: usize, visited: &mut Vec<bool>) {
visited[node] = true;
for neighbor in &graph[node] {
if visited[*neighbor] {
dfs(graph, *neighbor, visited);
}
}
}Solution
Step 1: Analyze the condition for recursive DFS call
The code calls dfs only ifvisited[*neighbor]is true, meaning already visited nodes are revisited, causing infinite recursion.Step 2: Correct the condition to avoid revisiting
Change condition toif !visited[*neighbor]to visit only unvisited neighbors.Final Answer:
Changeif visited[*neighbor]toif !visited[*neighbor]-> Option AQuick Check:
Visit unvisited neighbors only [OK]
- Calling DFS on visited nodes causing infinite loops
- Incorrectly marking nodes as unvisited
- Removing necessary loops
Solution
Step 1: Understand cycle detection in DFS
To detect cycles, track nodes currently in recursion stack to find back edges indicating cycles.Step 2: Implement recursion stack tracking
Use a boolean vector to mark nodes in current DFS path; if a neighbor is in this stack, a cycle exists.Step 3: Other options are incorrect
Marking visited after neighbors or using BFS won't detect cycles reliably; ignoring visited nodes causes infinite loops.Final Answer:
Use a recursion stack (call stack) boolean vector to track nodes in the current path and detect back edges -> Option DQuick Check:
Recursion stack tracks cycles in DFS [OK]
- Not tracking recursion stack causing missed cycles
- Using BFS which needs extra logic for cycles
- Ignoring visited nodes causing infinite loops
Ready to prove it?
Pass the test and this pattern moves unknown
