Cycle Detection in Directed Graph in DSA Java - Time & Space Complexity
We want to know 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.
boolean dfs(int node, boolean[] visited, boolean[] recStack, List<List<Integer>> graph) {
visited[node] = true;
recStack[node] = true;
for (int neighbor : graph.get(node)) {
if (!visited[neighbor] && dfs(neighbor, visited, recStack, graph))
return true;
else if (recStack[neighbor])
return true;
}
recStack[node] = false;
return false;
}
boolean isCyclic(int n, List<List<Integer>> graph) {
boolean[] visited = new boolean[n];
boolean[] recStack = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i] && dfs(i, visited, recStack, graph))
return true;
}
return false;
}
This code checks if a directed graph has a cycle using depth-first search.
- Primary operation: The depth-first search (dfs) visits each node and explores its neighbors recursively.
- How many times: Each node and edge is visited once during the search.
As the number of nodes and edges grows, the time to check all connections grows roughly in proportion.
| Input Size (n) | 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 work grows roughly linearly with the number of nodes plus edges.
Time Complexity: O(n + e)
This means the time grows linearly with the number of nodes (n) and edges (e) in the graph.
[X] Wrong: "The algorithm checks every possible path, so it takes exponential time."
[OK] Correct: The algorithm marks nodes visited and does not revisit them, so each edge and node is processed only once, keeping time linear.
Understanding this time complexity helps you explain how your cycle detection scales with graph size, showing you know efficient graph traversal.
"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 DQuick Check:
Cycle detection = finding directed cycles [OK]
- Confusing cycle with shortest path
- Ignoring edge directions
- Thinking cycle means any repeated node without direction
Solution
Step 1: Identify recursion stack tracking
During DFS, nodes in the current path are tracked to detect back edges indicating cycles.Step 2: Choose suitable data structure
A HashSet is used to quickly check if a node is in the current recursion stack.Final Answer:
HashSet -> Option BQuick Check:
Use HashSet to track recursion stack nodes [OK]
- Using Queue which is FIFO, not suitable here
- Using Stack but not checking membership efficiently
- Using ArrayList which is slower for membership check
import java.util.*;
public class GraphCycle {
private final int V = 3;
private final List adj = new ArrayList<>();
private final boolean[] visited = new boolean[V];
private final boolean[] recStack = new boolean[V];
public GraphCycle() {
for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
adj.get(0).add(1);
adj.get(1).add(2);
adj.get(2).add(0);
}
private boolean dfs(int node) {
if (recStack[node]) return true;
if (visited[node]) return false;
visited[node] = true;
recStack[node] = true;
for (int neighbor : adj.get(node)) {
if (dfs(neighbor)) return true;
}
recStack[node] = false;
return false;
}
public boolean hasCycle() {
for (int i = 0; i < V; i++) {
if (dfs(i)) return true;
}
return false;
}
public static void main(String[] args) {
GraphCycle graph = new GraphCycle();
System.out.println(graph.hasCycle());
}
}Solution
Step 1: Analyze graph edges
Edges: 0->1, 1->2, 2->0 form a cycle (0->1->2->0).Step 2: Trace DFS cycle detection
DFS from node 0 visits 1, then 2, then back to 0 which is in recursion stack, so returns true.Final Answer:
true -> Option AQuick Check:
Cycle exists in graph = true [OK]
- Ignoring recursion stack check
- Assuming no cycle because visited[] is true
- Confusing visited[] and recStack[] roles
boolean dfs(int node, boolean[] visited, boolean[] recStack, List adj) {
visited[node] = true;
recStack[node] = true;
for (int neighbor : adj.get(node)) {
if (!visited[neighbor]) {
dfs(neighbor, visited, recStack, adj);
} else if (recStack[neighbor]) {
return true;
}
}
recStack[node] = false;
return false;
}Solution
Step 1: Check recursive call handling
Recursive call dfs(neighbor, ...) is made but its return value is ignored.Step 2: Understand impact on cycle detection
If dfs(neighbor) returns true (cycle found), this should be returned immediately to propagate detection.Final Answer:
Missing return statement in recursive call -> Option CQuick Check:
Return recursive call result to detect cycles [OK]
- Ignoring return value of recursive dfs
- Not resetting recStack[node] to false
- Confusing visited and recStack roles
{
0: [1],
1: [2],
2: [3],
3: [1, 4],
4: []
}
Which nodes are part of at least one cycle?Solution
Step 1: Identify cycles in graph
Edges: 1->2, 2->3, 3->1 form a cycle involving nodes 1, 2, 3.Step 2: Check other nodes
Node 0 points to 1 but is not in cycle; node 4 has no outgoing edges.Final Answer:
[1, 2, 3] -> Option AQuick Check:
Cycle nodes = [1, 2, 3] [OK]
- Including nodes that only lead to cycle but not in it
- Missing nodes inside the cycle
- Confusing cycle presence with reachability
Ready to prove it?
Pass the test and this pattern moves unknown
