Bridges in Graph Tarjan's Algorithm in DSA Go - 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.
func dfs(u, parent int, graph [][]int, time *int, disc, low []int, bridges *[][2]int) {
disc[u] = *time
low[u] = *time
(*time)++
for _, v := range graph[u] {
if disc[v] == 0 {
dfs(v, u, graph, time, disc, low, bridges)
if low[v] > disc[u] {
*bridges = append(*bridges, [2]int{u, v})
}
if low[v] < low[u] {
low[u] = low[v]
}
} else if v != parent {
if disc[v] < low[u] {
low[u] = disc[v]
}
}
}
}
This code runs a depth-first search to find all bridges in an undirected graph using Tarjan's algorithm.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The depth-first search (dfs) visits each node and explores its neighbors.
- How many times: Each node is visited once, and each edge is checked once during recursion.
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, 3000 edges | About 4000 operations |
Pattern observation: Operations grow roughly in proportion to nodes plus edges, so doubling nodes and edges roughly doubles work.
Time Complexity: O(n + m)
This means the time grows linearly with the number of nodes and edges in the graph.
[X] Wrong: "The algorithm checks every possible pair of nodes, so it is O(n²)."
[OK] Correct: The algorithm only visits each node and edge once, not all pairs, so it is much faster than O(n²).
Understanding this linear time complexity helps you explain how efficient graph algorithms work in practice, 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 the definition of a bridge
A bridge is an edge that, when removed, increases the number of connected components in a graph.Step 2: Purpose of Tarjan's algorithm for bridges
Tarjan's algorithm efficiently finds all such edges (bridges) in a graph using DFS and low-link values.Final Answer:
To identify edges that, if removed, increase the number of connected components -> Option AQuick Check:
Bridges = edges increasing components when removed [OK]
- Confusing bridges with cycles
- Thinking Tarjan finds shortest paths
- Mixing bridges with node counts
Solution
Step 1: Understand adjacency list representation
Adjacency list for n nodes is a slice of slices: each node has a slice of connected nodes.Step 2: Correct Go syntax for adjacency list
make([][]int, n) creates a slice of n slices of ints, perfect for adjacency lists.Final Answer:
adj := make([][]int, n) -> Option BQuick Check:
Adjacency list = slice of slices [OK]
- Using make([]int, n) which is flat slice
- Incorrect slice literal syntax
- Confusing capacity with length
package main
import "fmt"
var (
time int
adj [][]int
disc []int
low []int
visited []bool
)
func dfs(u, parent int) {
visited[u] = true
disc[u] = time
low[u] = time
time++
for _, v := range adj[u] {
if v == parent {
continue
}
if !visited[v] {
dfs(v, u)
if low[v] > disc[u] {
fmt.Printf("Bridge found: %d - %d\n", u, v)
}
if low[v] < low[u] {
low[u] = low[v]
}
} else {
if disc[v] < low[u] {
low[u] = disc[v]
}
}
}
}
func main() {
n := 4
adj = make([][]int, n)
adj[0] = []int{1, 2}
adj[1] = []int{0, 2, 3}
adj[2] = []int{0, 1}
adj[3] = []int{1}
disc = make([]int, n)
low = make([]int, n)
visited = make([]bool, n)
dfs(0, -1)
}Solution
Step 1: Analyze the graph structure
The graph has a triangle between nodes 0,1,2 and a single edge from 1 to 3. The triangle edges are not bridges because removing them doesn't disconnect the graph.Step 2: Identify bridges using Tarjan's logic
Edge 1-3 is a bridge because removing it disconnects node 3. The code prints "Bridge found: 1 - 3" only.Final Answer:
Bridge found: 1 - 3 -> Option DQuick Check:
Only edge 1-3 disconnects graph [OK]
- Marking triangle edges as bridges
- Ignoring parent check in DFS
- Confusing discovery and low values
func dfs(u, parent int) {
visited[u] = true
disc[u] = time
low[u] = time
time++
for _, v := range adj[u] {
if !visited[v] {
dfs(v, u)
if low[v] > disc[u] {
fmt.Printf("Bridge: %d - %d\n", u, v)
}
if low[v] < low[u] {
low[u] = low[v]
}
} else if v != parent {
if disc[v] < low[u] {
low[u] = disc[v]
}
}
}
}Solution
Step 1: Check parent skipping logic
The code does not skip the parent node before calling DFS recursively, which can cause revisiting the parent and incorrect low values.Step 2: Identify missing parent check before DFS
Correct code should have "if v == parent { continue }" before DFS call to avoid revisiting parent edge.Final Answer:
The parent check is missing before recursive DFS call -> Option CQuick Check:
Skip parent before DFS to avoid cycles [OK]
- Confusing parent check placement
- Not incrementing time variable
- Misusing else if condition
Solution
Step 1: Understand Tarjan's algorithm approach
Tarjan's algorithm uses a single DFS traversal from any node, tracking discovery and low times to find bridges efficiently.Step 2: Identify condition for bridges
An edge u-v is a bridge if low[v] > disc[u], meaning v cannot reach u or its ancestors without that edge.Final Answer:
Use DFS once from any node, track discovery and low times, and identify edges where low[v] > disc[u] -> Option AQuick Check:
Single DFS + low/disc times = bridges [OK]
- Running DFS multiple times unnecessarily
- Using BFS which doesn't track low times
- Brute force edge removal checking connectivity
Ready to prove it?
Pass the test and this pattern moves unknown
