Topics › Graphs › Topo Sort and Problems
Detect A cycle in a Directed Graph using BFS
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a directed graph with V vertices numbered 0 to V - 1, where adj.get(u) lists every v with an edge u -> v. Return true if the graph contains a directed cycle, a walk along the arrows that returns to its start. The graph may be disconnected and may contain self-loops or repeated edges. Use a breadth-first technique.
Constraints: 1 ≤ V ≤ 10^5, 0 ≤ E ≤ 10^5.
Example 1
V = 5, edges: 0->1, 1->2, 2->3, 3->1, 3->4
answer = true // 1 -> 2 -> 3 -> 1
Example 2
V = 6, edges: 5->0, 4->0, 5->2, 4->1, 2->3, 3->1
answer = false // a DAG: every arrow can be drawn pointing "downwards"
BFS has no recursion stack, so the DFS trick of checking “is this vertex on my current path?” is not available. The breadth-first answer comes from a different angle, in-degrees, and it is the same machinery that produces a topological order.
Approach 1: Brute force, BFS from every vertex
Intuition. Vertex s lies on a cycle if and only if there is a walk from s back to s. A BFS from s visits everything reachable from s; if any edge it scans points to s itself, the walk exists. Repeat for every vertex.
Algorithm.
- For each vertex
s: start a BFS with a freshseenarray. - For each dequeued
xand each edgex -> y: ify == s, returntrue; otherwise enqueueyif unseen. - After all start vertices, return
false.
static boolean bruteForce(int V, List<List<Integer>> adj) {
for (int s = 0; s < V; s++) {
boolean[] seen = new boolean[V];
Deque<Integer> queue = new ArrayDeque<>();
seen[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
int x = queue.poll();
for (int y : adj.get(x)) {
if (y == s) return true; // walked from s back to s
if (!seen[y]) {
seen[y] = true;
queue.add(y);
}
}
}
}
return false;
}
Complexity. Time O(V · (V + E)), one BFS per vertex. Space O(V) for one BFS at a time.
For V = E = 10^5 that is about 10^10 steps. The fix is to stop asking “does each vertex come back to itself?” and instead peel away the vertices that obviously cannot be on a cycle.
Approach 2: Optimal, Kahn’s algorithm
Intuition. Look at a vertex with in-degree 0, one that no arrow enters. It cannot be on a cycle, because a cycle enters each of its vertices. So we may delete it, together with its outgoing edges, without destroying any cycle. Deleting those edges lowers the in-degree of its successors, and some of them may now have in-degree 0 too. Keep peeling.
Two facts make this a complete test:
- In a DAG, peeling never gets stuck. Every non-empty DAG has a vertex with in-degree 0: start anywhere and keep walking backwards along incoming arrows; without a cycle you can never revisit a vertex, so the walk must stop, and it stops only at a vertex with nothing coming in. Removing vertices keeps the graph a DAG, so peeling continues until every vertex is gone.
- A cycle is never peeled. For a vertex on a cycle to be removed, its in-degree must reach 0, which requires its predecessor on the cycle to be removed first. That predecessor has the same problem, and so on around the loop. None of them can go first.
So: count how many vertices get removed. If it is V, the graph is acyclic; if fewer, a cycle exists. A queue holds the vertices whose in-degree has dropped to 0 and are waiting to be removed, which is why this is called the BFS method.
Algorithm.
- Compute
indegree[v]for every vertex by scanning all edges. - Enqueue every vertex with in-degree 0.
- While the queue is not empty: dequeue
u, incrementremoved, and for each edgeu -> vdecrementindegree[v]; if it reaches 0, enqueuev. - Return
removed < V.
static boolean optimal(int V, List<List<Integer>> adj) {
int[] indegree = new int[V];
for (int u = 0; u < V; u++) {
for (int v : adj.get(u)) indegree[v]++;
}
Deque<Integer> queue = new ArrayDeque<>();
for (int u = 0; u < V; u++) {
if (indegree[u] == 0) queue.add(u);
}
int removed = 0;
while (!queue.isEmpty()) {
int u = queue.poll();
removed++;
for (int v : adj.get(u)) {
if (--indegree[v] == 0) queue.add(v); // v has no remaining prerequisites
}
}
return removed < V; // vertices on or behind a cycle never reach in-degree 0
}
Dry run on example 1. Initial in-degrees: 0:0, 1:2, 2:1, 3:1, 4:1.
| step | dequeued | in-degree changes | queue after | removed |
|---|---|---|---|---|
| start | [0] | 0 | ||
| 1 | 0 | 1: 2 -> 1 | [] | 1 |
The queue is empty after one removal. Vertex 1 still has an arrow from 3, vertex 3 has one from 2, and vertex 2 has one from 1: the cycle keeps all three alive. removed = 1 < 5, so the answer is true. Notice that vertex 4 is not on the cycle but is also never removed, because it hangs behind the cycle. The leftover set is “cycle plus everything downstream of it”, not just the cycle itself.
On example 2, the in-degrees are 0:2, 1:2, 2:1, 3:1, 4:0, 5:0. The removal order is 4, 5, 0, 2, 3, 1, all six vertices, so the answer is false. That removal order is also a valid topological order of the graph.
Complexity. Time O(V + E): computing in-degrees scans every edge once, each vertex is enqueued at most once, and each edge is decremented once when its source is dequeued. Space O(V) for the in-degree array and the queue.
Edge cases and pitfalls
- Count, do not just check the queue. The queue becomes empty in both cases. The signal is how many vertices were removed.
- No source at all. In
0 -> 1 -> 0plus an isolated edge2 -> 3, vertices 2 and 3 are peeled but 0 and 1 never are. A graph where every vertex is on a cycle starts with an empty queue, andremoved = 0correctly reports a cycle. - Self-loops. An edge
u -> uadds 1 tou’s own in-degree, which onlyucould remove.uis stuck forever, so the cycle is reported. - Repeated edges. Two copies of
0 -> 1add 2 toindegree[1]and are both decremented when0is removed, so nothing breaks. - Isolated vertices have in-degree 0 and are removed immediately.
- Do not use the undirected parent check. BFS with “a visited neighbour that is not my parent” is wrong for directed graphs: in the diamond
0->1, 0->2, 1->3, 2->3, vertex 3 is reached twice without any cycle. - Overflow is not a concern here: in-degrees are at most
E, which fits in anint.
Related problems
- Detect A cycle in a Directed Graph using DFS solves the same task with an “on the current path” marker.
- Topological Sort BFS is the same algorithm; it returns the removal order instead of a yes/no answer.
- Course Schedule I asks exactly this question about a prerequisite graph.
- Detect A cycle in Undirected Graph using BFS is the undirected version, where parent tracking does work.