In this article, we will learn to use Depth-First Search algorithms to detect a cycle in a directed graph

Unlike in an undirected graph, to detect a cycle in a directed graph, we should consider the edges direction

Problem

  • Given a directed graph G = (V, E)

  • Write an algorithm to detect a cycle in that graph

Example 1

  • Input: Given a directed graph G = (V, E) with V = 5 and E = [[0, 1], [1, 2], [2, 0], [1, 3], [2, 4]]

  • Output: true

  • Explanation: 3 edges [0, 1], [1, 2], [2, 0] formed a cycle

Example 2

  • Input: Given a directed graph G = (V, E) with V = 4 and E = [[0, 1], [2, 0], [2, 1], [3, 2]]

  • Output: false

  • Explanation: there are no cycles in the given graph

Approach 1: DFS Iterative

  • Use a stack to track the traversal path

  • Keeping track of the visited and on-stack state of each vertex

    While visiting a vertex and its adjacencies, if we ever encounter an on-stack vertex then the graph has a cycle

import java.util.ArrayDeque;  
import java.util.Deque;

public class DetectCycleDirectedByDFSIterative {  
    static boolean dfsByIterative(GraphDirectedByAdjacencyList g) {
        boolean[] visited = new boolean[g.getV()];
        boolean[] onStack = new boolean[g.getV()];
        Deque<Integer> stack = new ArrayDeque<>();

        for (int i = 0; i < g.getV(); i++) {
            if (visited[i]) continue;
            stack.push(i);

            while (!stack.isEmpty()) {
                int v = stack.peek();

                if (!visited[v]) {
                    visited[v] = true;
                    onStack[v] = true;
                } else {
                    onStack[v] = false;
                    stack.pop();
                }

                for (Integer w : g.getAdjacencyList().get(v)) {
                    if (!visited[w]) {
                        stack.push(w);
                    } else if (onStack[w]) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public static void main(String[] args) {
        GraphDirectedByAdjacencyList g1 = new GraphDirectedByAdjacencyList(5);
        g1.addEdge(0, 1);
        g1.addEdge(1, 2);
        g1.addEdge(2, 0);
        g1.addEdge(1, 3);
        g1.addEdge(2, 4);
        System.out.println(dfsByIterative(g1));

        GraphDirectedByAdjacencyList g2 = new GraphDirectedByAdjacencyList(4);
        g2.addEdge(0, 1);
        g2.addEdge(2, 0);
        g2.addEdge(2, 1);
        g2.addEdge(3, 2);
        System.out.println(dfsByIterative(g2));
    }
}

GraphDirectedByAdjacencyList is defined in Graph Data Structure Tutorial

  • Output
true  
false
  • Time complexity: O(V+E)

  • Space complexity: O(V)

Approach 2: DFS Recursive

  • Instead of using a stack, the algorithm calls to itself to explore unvisited vertices

  • Keeping track of the visited and on-stack state of each vertex

    While visiting a vertex and its adjacencies, if we ever encounter an on-stack vertex then the graph has a cycle

import java.util.List;

public class DetectCycleDirectedByDFSRecursive {  
    static boolean dfsByRecursive(GraphDirectedByAdjacencyList g, int v, boolean[] visited, boolean[] onStack) {
        if (onStack[v]) return true;
        if (visited[v]) return false;

        visited[v] = true;
        onStack[v] = true;

        List<Integer> children = g.getAdjacencyList().get(v);
        for (Integer c: children) {
            if (dfsByRecursive(g, c, visited, onStack)) {
                return true;
            }
        }

        onStack[v] = false;
        return false;
    }

    static boolean hasCycle(GraphDirectedByAdjacencyList g) {
        boolean[] visited = new boolean[g.getV()];
        boolean[] onStack = new boolean[g.getV()];

        for (int i = 0; i < g.getV(); i++) {
            if (dfsByRecursive(g, i, visited, onStack)) {
                return true;
            }
        }

        return false;
    }

    public static void main(String[] args) {
        GraphDirectedByAdjacencyList g1 = new GraphDirectedByAdjacencyList(5);
        g1.addEdge(0, 1);
        g1.addEdge(1, 2);
        g1.addEdge(2, 0);
        g1.addEdge(1, 3);
        g1.addEdge(2, 4);
        System.out.println(hasCycle(g1));

        GraphDirectedByAdjacencyList g2 = new GraphDirectedByAdjacencyList(4);
        g2.addEdge(0, 1);
        g2.addEdge(2, 0);
        g2.addEdge(2, 1);
        g2.addEdge(3, 2);
        System.out.println(hasCycle(g2));
    }
}

GraphDirectedByAdjacencyList is defined in Graph Data Structure Tutorial

  • Output
true  
false
  • Time complexity: O(V+E)

  • Space complexity: O(V)

Approach 3: DFS Iterative with Color

  • Use a stack to track the traversal path

  • Keeping track of the unvisited (White color), visit in progress (Gray color, the vertex is on the stack) and visited state (Black color) of each vertex.

    While visiting a vertex and its adjacencies, if we ever encounter a gray vertex then the graph has a cycle

import java.util.ArrayDeque;  
import java.util.Deque;

public class DetectCycleDirectedByDFSIterativeColor {  
    static final int WHITE = 0, GRAY = 1, BLACK = 2;

    static boolean dfsByIterativeWithColor(GraphDirectedByAdjacencyList g) {
        int[] color = new int[g.getV()];
        Deque<Integer> stack = new ArrayDeque<>(g.getV());

        for (int i = 0; i < g.getV(); i++) {
            if (color[i] != WHITE) continue;
            stack.push(i);

            while (!stack.isEmpty()) {
                int v = stack.peek();

                if (color[v] == WHITE) {
                    color[v] = GRAY;
                } else {
                    color[v] = BLACK;
                    stack.pop();
                }

                for (int w : g.getAdjacencyList().get(v)) {
                    if (color[w] == GRAY) {
                        return true;
                    } else if (color[w] == WHITE) {
                        stack.push(w);
                    }
                }
            }
        }

        return false;
    }

    public static void main(String[] args) {
        GraphDirectedByAdjacencyList g1 = new GraphDirectedByAdjacencyList(5);
        g1.addEdge(0, 1);
        g1.addEdge(1, 2);
        g1.addEdge(2, 0);
        g1.addEdge(1, 3);
        g1.addEdge(2, 4);
        System.out.println(dfsByIterativeWithColor(g1));

        GraphDirectedByAdjacencyList g2 = new GraphDirectedByAdjacencyList(4);
        g2.addEdge(0, 1);
        g2.addEdge(2, 0);
        g2.addEdge(2, 1);
        g2.addEdge(3, 2);
        System.out.println(dfsByIterativeWithColor(g2));
    }
}

GraphDirectedByAdjacencyList is defined in Graph Data Structure Tutorial

  • Output
true  
false
  • Time complexity: O(V+E)

  • Space complexity: O(V)

Approach 4: DFS Recursive with Color

  • Instead of using a stack, the algorithm calls to itself to explore unvisited vertices

  • Keeping track of the unvisited (White color), visit in progress (Gray color, the vertex is on the stack) and visited state (Black color) of each vertex

    While visiting a vertex and its adjacencies, if we ever encounter a gray vertex then the graph has a cycle

public class DetectCycleDirectedByDFSRecursiveColor {  
    static final int WHITE = 0, GRAY = 1, BLACK = 2;

    static boolean dfsByRecursiveWithColor(GraphDirectedByAdjacencyList g, int v, int[] color) {
        color[v] = GRAY;

        for (Integer w : g.getAdjacencyList().get(v)) {
            if (color[w] == GRAY) {
                return true;
            }

            if (color[w] == WHITE && dfsByRecursiveWithColor(g, w, color)) {
                return true;
            }
        }

        color[v] = BLACK;
        return false;
    }

    static boolean hasCycle(GraphDirectedByAdjacencyList g) {
        int[] color = new int[g.getV()];

        for (int i = 0; i < g.getV(); i++) {
            if (color[i] == WHITE && dfsByRecursiveWithColor(g, i, color)) {
                return true;
            }
        }

        return false;
    }

    public static void main(String[] args) {
        GraphDirectedByAdjacencyList g1 = new GraphDirectedByAdjacencyList(5);
        g1.addEdge(0, 1);
        g1.addEdge(1, 2);
        g1.addEdge(2, 0);
        g1.addEdge(1, 3);
        g1.addEdge(2, 4);
        System.out.println(hasCycle(g1));

        GraphDirectedByAdjacencyList g2 = new GraphDirectedByAdjacencyList(4);
        g2.addEdge(0, 1);
        g2.addEdge(2, 0);
        g2.addEdge(2, 1);
        g2.addEdge(3, 2);
        System.out.println(hasCycle(g2));
    }
}

GraphDirectedByAdjacencyList is defined in Graph Data Structure Tutorial

  • Output
true  
false
  • Time complexity: O(V+E)

  • Space complexity: O(V)