Bridges in Graph Tarjan's Algorithm in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find bridges in a graph grows as the graph gets bigger.
Specifically, how the algorithm's steps increase with more nodes and edges.
Analyze the time complexity of the following code snippet.
fn dfs(
u: usize,
parent: usize,
time: &mut usize,
graph: &Vec<Vec<usize>>,
visited: &mut Vec<bool>,
disc: &mut Vec<usize>,
low: &mut Vec<usize>,
bridges: &mut Vec<(usize, usize)>
) {
visited[u] = true;
*time += 1;
disc[u] = *time;
low[u] = *time;
for &v in &graph[u] {
if v == parent { continue; }
if !visited[v] {
dfs(v, u, time, graph, visited, disc, low, bridges);
low[u] = low[u].min(low[v]);
if low[v] > disc[u] {
bridges.push((u, v));
}
} else {
low[u] = low[u].min(disc[v]);
}
}
}
This code runs a depth-first search to find all bridges in an undirected graph using Tarjan's algorithm.
- Primary operation: Recursive DFS visits each node and explores its neighbors.
- How many times: Each node is visited once, and each edge is checked once or twice.
As the number of nodes (n) and edges (m) grow, the algorithm visits each node once and checks each edge once.
| Input Size (n, m) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 operations (visits + edge checks) |
| 100 nodes, 200 edges | About 300 operations |
| 1000 nodes, 5000 edges | About 6000 operations |
Pattern observation: The operations grow roughly in proportion to nodes plus edges, so doubling nodes and edges roughly doubles the work.
Time Complexity: O(n + m)
This means the time needed grows linearly with the total number of nodes and edges in the graph.
[X] Wrong: "The algorithm checks every possible pair of nodes, so it must be O(n²)."
[OK] Correct: The algorithm only visits each node and edge once, not all pairs, so it runs much faster than O(n²).
Understanding this linear time complexity helps you explain how efficient graph algorithms work in practice.
It shows you can handle large graphs without slowdowns, a key skill in many coding challenges.
"What if the graph was directed instead of undirected? How would the time complexity change?"
Practice
Solution
Step 1: Understand what a bridge is in a graph
A bridge is an edge that, when removed, increases the number of connected components in the graph.Step 2: Identify Tarjan's algorithm goal
Tarjan's algorithm finds such edges efficiently by using DFS and tracking discovery and low times.Final Answer:
To find edges that, if removed, increase the number of connected components -> Option CQuick Check:
Bridges = edges increasing components [OK]
- Confusing bridges with cycles
- Thinking it finds shortest paths
- Mixing bridges with articulation points
Solution
Step 1: Understand adjacency list representation
Each node has a vector of neighbors, so graph is a vector of vectors.Step 2: Check vector initialization for 5 nodes
vec![vec![]; 5] creates 5 empty vectors, one per node.Final Answer:
let graph: Vec = vec![vec![]; 5]; -> Option DQuick Check:
Adjacency list for 5 nodes = vec![vec![]; 5] [OK]
- Using wrong vector type (Vec<usize> instead of Vec)
- Initializing with wrong size
- Filling inner vectors with zeros instead of empty
let edges = vec![(0,1), (1,2), (2,0), (1,3)];
// Graph: triangle 0-1-2 and a bridge edge 1-3
// After running Tarjan's algorithm, which edges are bridges?
println!("Bridges: {:?}", vec![(1,3)]);Solution
Step 1: Analyze the graph structure
The edges (0,1), (1,2), (2,0) form a cycle (triangle), so none are bridges.Step 2: Identify the bridge edge
The edge (1,3) connects node 3 to the triangle and is a bridge because removing it disconnects node 3.Final Answer:
Bridges: [(1, 3)] -> Option AQuick Check:
Only edge (1,3) breaks connectivity [OK]
- Marking cycle edges as bridges
- Ignoring leaf node connections
- Confusing bridge edges with articulation points
fn dfs(u: usize, parent: usize, graph: &Vec, disc: &mut Vec<usize>, low: &mut Vec<usize>, time: &mut usize, bridges: &mut Vec<(usize, usize)>) {
disc[u] = *time;
low[u] = *time;
*time += 1;
for &v in &graph[u] {
if v == parent {
continue;
}
if disc[v] == 0 {
dfs(v, u, graph, disc, low, time, bridges);
low[u] = low[u].min(low[v]);
if low[v] > disc[u] {
bridges.push((u, v));
}
} else {
low[u] = low[u].min(disc[v]);
}
}
}Solution
Step 1: Check initialization of disc and low arrays
disc and low must be initialized with zeros to detect unvisited nodes (disc[v] == 0).Step 2: Verify other logic correctness
Parent check and time passing are correct; condition low[v] > disc[u] is correct for bridges.Final Answer:
disc and low arrays are not initialized with zeros -> Option AQuick Check:
Uninitialized disc causes wrong visited checks [OK]
- Not initializing disc and low arrays
- Wrong parent check condition
- Passing time by value instead of mutable reference
Solution
Step 1: Analyze graph components
Nodes 0,1,2 form a cycle (triangle), so edges among them are not bridges. Nodes 3,4,5 form a cycle, so edges among them are not bridges.Step 2: Identify bridge edges
The edge (1,3) connects the two cycles. Removing (1,3) disconnects the graph, so it is a bridge.Final Answer:
[(1, 3)] -> Option BQuick Check:
Only edge connecting components is bridge [OK]
- Marking cycle edges as bridges
- Ignoring connectivity between components
- Confusing bridges with articulation points
Ready to prove it?
Pass the test and this pattern moves unknown
