Cycle Detection in Directed Graph in DSA Go - Time & Space Complexity
We want to understand how long it takes to find a cycle in a directed graph.
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)
recStack := make(map[int]bool)
var dfs func(int) bool
dfs = func(node int) bool {
if recStack[node] {
return true
}
if visited[node] {
return false
}
visited[node] = true
recStack[node] = true
for _, neighbor := range graph[node] {
if dfs(neighbor) {
return true
}
}
recStack[node] = false
return false
}
for node := range graph {
if dfs(node) {
return true
}
}
return false
}
This code checks if a directed graph has a cycle using depth-first search.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The depth-first search (DFS) visits each node and its neighbors recursively.
- How many times: Each node and edge is visited once during the DFS traversal.
As the graph grows, the DFS explores more nodes and edges.
| 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, 5000 edges | About 6000 visits |
Pattern observation: The operations grow roughly in proportion to the number of 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: "The DFS visits each node multiple times, so the time is much higher than linear."
[OK] Correct: Each node is marked visited, so DFS does not repeat work on the same node, keeping the time linear.
Understanding this time complexity helps you explain how efficient your cycle detection is and shows you can analyze graph algorithms clearly.
"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 cycle in directed graph
A cycle means a path that starts and ends at the same node, following edge directions.Step 2: Identify what cycle detection finds
Cycle detection checks if such a path exists in the graph.Final Answer:
Whether there is a path that starts and ends at the same node following the direction of edges -> Option AQuick Check:
Cycle detection = finding directed loops [OK]
- Confusing cycle with shortest path
- Ignoring edge directions
- Thinking cycle means any repeated node
Solution
Step 1: Understand adjacency list representation
An adjacency list for a directed graph maps each node to a list of nodes it points to.Step 2: Check Go syntax for adjacency list
Using map[int][]int creates a map from int to slice of ints, perfect for adjacency list.Final Answer:
graph := make(map[int][]int) -> Option AQuick Check:
Adjacency list = map[int][]int [OK]
- Using slice of int only (no mapping)
- Using map[int]int which stores single int per node
- Using [][]int without map keys
package main
import "fmt"
func dfs(node int, graph map[int][]int, visited, recStack map[int]bool) bool {
if recStack[node] {
return true
}
if visited[node] {
return false
}
visited[node] = true
recStack[node] = true
for _, neighbor := range graph[node] {
if dfs(neighbor, graph, visited, recStack) {
return true
}
}
recStack[node] = false
return false
}
func main() {
graph := map[int][]int{
1: {2},
2: {3},
3: {1},
}
visited := make(map[int]bool)
recStack := make(map[int]bool)
fmt.Println(dfs(1, graph, visited, recStack))
}
Solution
Step 1: Analyze graph structure
Graph edges: 1->2, 2->3, 3->1 form a cycle.Step 2: Trace DFS cycle detection
DFS from node 1 visits 2, then 3, then back to 1 which is in recursion stack, so returns true.Final Answer:
true -> Option BQuick Check:
Cycle detected = true [OK]
- Ignoring recursion stack for cycle detection
- Assuming visited alone detects cycle
- Confusing false with no cycle
func dfs(node int, graph map[int][]int, visited map[int]bool) bool {
if visited[node] {
return true
}
visited[node] = true
for _, neighbor := range graph[node] {
if dfs(neighbor, graph, visited) {
return true
}
}
return false
}Solution
Step 1: Understand cycle detection logic
Cycle detection in directed graph needs to track nodes in current recursion path (recursion stack).Step 2: Identify missing recursion stack
This code only uses visited to mark visited nodes, which cannot detect cycles correctly in directed graphs.Final Answer:
Missing recursion stack to track nodes in current path -> Option CQuick Check:
Recursion stack needed for directed cycle detection [OK]
- Using only visited to detect cycles
- Confusing undirected and directed cycle detection
- Ignoring recursion stack leads to false negatives
graph := map[int][]int{
1: {2, 3},
2: {4},
3: {4, 5},
4: {6},
5: {6},
6: {3},
}
Which nodes are part of a cycle in this graph?Solution
Step 1: Analyze edges for cycles
Edges: 3->4, 4->6, 6->3 form a cycle involving nodes 3, 4, 6.Step 2: Confirm cycle nodes
Nodes 1, 2, 5 lead into cycle but are not part of cycle themselves.Final Answer:
[3, 4, 6] -> Option DQuick Check:
Cycle nodes = [3,4,6] [OK]
- Including nodes that only lead to cycle
- Missing nodes that form the cycle
- Confusing cycle membership with reachability
Ready to prove it?
Pass the test and this pattern moves unknown
