Graph Patterns: Cycle Detection in Directed Graphs
Detecting a cycle in an undirected graph is easy: if you see a visited node, it's a cycle. But in Directed Graphs, distinct paths can merge without forming loops. We must track the active recursion stack.
Introduction
In a directed graph, multiple distinct paths can lead to the same destination without ever forming a loop. If we only use a standard visited array, our algorithm will trigger false positives when these paths merge.
The Path Tracker
The secret is maintaining two separate boolean arrays:
- vis: Tracks nodes we have completely processed to avoid redundant work.
- pathVis: Tracks nodes that are currently active in our exact present recursion stack.
When we traverse down a path, we mark both arrays as true. If we hit a dead end and need to backtrack, we leave vis as true, but we revert pathVis to false because we are stepping out of that path.
If we ever encounter a neighbor that is marked true in both vis AND pathVis, we have crashed back into our own active tail. We have found a confirmed cycle.
Implementation
class Solution {
public boolean isCyclic(int V, ArrayList<ArrayList<Integer>> adj) {
boolean[] vis = new boolean[V];
boolean[] pathVis = new boolean[V];
for (int i = 0; i < V; i++) {
if (!vis[i]) {
if (dfsCheck(i, adj, vis, pathVis)) return true;
}
}
return false;
}
private boolean dfsCheck(int node, ArrayList<ArrayList<Integer>> adj, boolean[] vis, boolean[] pathVis) {
vis[node] = true;
pathVis[node] = true; // Mark node as part of current active path
for (int neighbor : adj.get(node)) {
if (!vis[neighbor]) {
if (dfsCheck(neighbor, adj, vis, pathVis)) return true;
}
else if (pathVis[neighbor]) {
// Node is visited AND currently in the active path
return true;
}
}
// Backtrack: Remove node from active path as we retreat
pathVis[node] = false;
return false;
}
}