Cycle Detection in Undirected Graph in DSA Go - Time & Space Complexity
We want to understand how long it takes to check if an undirected graph has a cycle.
Specifically, how the time grows as the graph gets bigger.
Analyze the time complexity of the following code snippet.
func hasCycle(graph map[int][]int) bool {
visited := make(map[int]bool)
var dfs func(node, parent int) bool
dfs = func(node, parent int) bool {
visited[node] = true
for _, neighbor := range graph[node] {
if !visited[neighbor] {
if dfs(neighbor, node) {
return true
}
} else if neighbor != parent {
return true
}
}
return false
}
for node := range graph {
if !visited[node] {
if dfs(node, -1) {
return true
}
}
}
return false
}
This code uses depth-first search to find if any cycle exists in an undirected graph.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: DFS visits each node and explores its neighbors.
- How many times: Each node and edge is visited at most once during DFS traversal.
As the number of nodes and edges grow, the DFS explores all connections once.
| Input Size (n nodes, e edges) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 visits (nodes + edges) |
| 100 nodes, 200 edges | About 300 visits |
| 1000 nodes, 1500 edges | About 2500 visits |
Pattern observation: The work grows roughly in proportion to nodes plus edges.
Time Complexity: O(n + e)
This means the time to detect a cycle grows linearly with the number of nodes and edges in the graph.
[X] Wrong: "Cycle detection takes quadratic time because of nested loops."
[OK] Correct: DFS visits each node and edge only once, so it does not repeat work unnecessarily.
Understanding this time complexity helps you explain efficient graph traversal in interviews confidently.
"What if the graph was directed instead of undirected? How would the time complexity change?"
Practice
Solution
Step 1: Understand DFS traversal in undirected graph
DFS visits nodes and keeps track of the parent node to avoid false cycle detection.Step 2: Detect cycle condition
If a visited node is found again and it is not the parent, it means a cycle exists.Final Answer:
If we visit a node that is already visited and is not the parent, a cycle exists. -> Option AQuick Check:
Cycle detection in undirected graph = visit visited node not parent [OK]
- Ignoring parent node when checking visited nodes
- Assuming disconnected graph always has cycles
- Confusing cycle detection with number of edges
Solution
Step 1: Understand adjacency list representation
An adjacency list for an undirected graph is a slice of slices where each index holds neighbors.Step 2: Check code correctness
graph := make([][]int, 3) graph[0] = []int{1, 2} graph[1] = []int{0} graph[2] = []int{0} correctly creates a slice of slices and assigns neighbors for each node.Final Answer:
graph := make([][]int, 3) graph[0] = []int{1, 2} graph[1] = []int{0} graph[2] = []int{0} -> Option AQuick Check:
Adjacency list = slice of slices with neighbors [OK]
- Using map[int]int instead of map[int][]int or [][]int
- Using []int instead of [][]int for adjacency list
- Not initializing slices before assignment
package main
import "fmt"
func dfs(node, parent int, graph [][]int, visited []bool) bool {
visited[node] = true
for _, neighbor := range graph[node] {
if !visited[neighbor] {
if dfs(neighbor, node, graph, visited) {
return true
}
} else if neighbor != parent {
return true
}
}
return false
}
func main() {
graph := [][]int{{1, 2}, {0, 2}, {0, 1}}
visited := make([]bool, 3)
if dfs(0, -1, graph, visited) {
fmt.Println("Cycle detected")
} else {
fmt.Println("No cycle")
}
}Solution
Step 1: Analyze graph structure
The graph has edges: 0-1, 0-2, and 1-2 forming a triangle, which is a cycle.Step 2: Trace DFS cycle detection
DFS from node 0 visits 1 and 2; when visiting 2 from 1, it finds 0 already visited and not parent, detecting a cycle.Final Answer:
Cycle detected -> Option DQuick Check:
Triangle graph = cycle detected [OK]
- Ignoring the neighbor != parent condition
- Assuming no cycle if start node has no parent
- Misreading graph edges
func dfs(node int, graph [][]int, visited []bool) bool {
visited[node] = true
for _, neighbor := range graph[node] {
if !visited[neighbor] {
if dfs(neighbor, graph, visited) {
return true
}
} else {
return true
}
}
return false
}Solution
Step 1: Understand DFS cycle detection logic
In undirected graphs, revisiting a visited node is not always a cycle if it is the parent node.Step 2: Identify missing parent parameter
The code lacks a parent parameter to exclude the immediate parent from cycle detection, causing false positives.Final Answer:
Missing parent parameter to avoid false cycle detection -> Option BQuick Check:
Parent parameter needed to avoid false cycle [OK]
- Not passing parent node in DFS
- Returning true on any visited neighbor
- Confusing directed and undirected cycle detection
graph := [][]int{
{1},
{0, 2, 3},
{1, 3},
{1, 2, 4},
{3}
}
How many connected components does the graph have and does it contain a cycle?Solution
Step 1: Count connected components
All nodes 0 to 4 are reachable from each other, so only 1 connected component exists.Step 2: Detect cycle in the graph
Edges form a cycle among nodes 1, 2, 3 (triangle), so the graph contains a cycle.Final Answer:
1 connected component, contains a cycle -> Option CQuick Check:
Single connected graph with triangle cycle [OK]
- Counting connected components incorrectly
- Missing cycle formed by nodes 1, 2, 3
- Assuming no cycle if graph is connected
Ready to prove it?
Pass the test and this pattern moves unknown
