54

Note that a graph is represented as an adjacency list.

I've heard of 2 approaches to find a cycle in a graph:

  1. Keep an array of boolean values to keep track of whether you visited a node before. If you run out of new nodes to go to (without hitting a node you have already been), then just backtrack and try a different branch.

  2. The one from Cormen's CLRS or Skiena: For depth-first search in undirected graphs, there are two types of edges, tree and back. The graph has a cycle if and only if there exists a back edge.

Can somebody explain what are the back edges of a graph and what's the diffirence between the above 2 methods.

Thanks.

Update: Here's the code to detect cycles in both cases. Graph is a simple class that represents all graph-nodes as unique numbers for simplicity, each node has its adjacent neighboring nodes (g.getAdjacentNodes(int)):

public class Graph {

  private int[][] nodes; // all nodes; e.g. int[][] nodes = {{1,2,3}, {3,2,1,5,6}...};

  public int[] getAdjacentNodes(int v) {
    return nodes[v];
  }

  // number of vertices in a graph
  public int vSize() {
    return nodes.length;
  }

}

Java code to detect cycles in an undirected graph:

    public class DFSCycle {

    private boolean marked[];
    private int s;
    private Graph g;
    private boolean hasCycle;

    // s - starting node
    public DFSCycle(Graph g, int s) {
        this.g = g;
        this.s = s;
        marked = new boolean[g.vSize()];
        findCycle(g,s,s);
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public void findCycle(Graph g, int v, int u) {

        marked[v] = true;

        for (int w : g.getAdjacentNodes(v)) {
            if(!marked[w]) {
                marked[w] = true;
                findCycle(g,w,v);
            } else if (v != u) {
                hasCycle = true;
                return;
            }
        }

    }  
}

Java code to detect cycles in a directed graph:

public class DFSDirectedCycle {

    private boolean marked[];
    private boolean onStack[];
    private int s;
    private Graph g;
    private boolean hasCycle;

    public DFSDirectedCycle(Graph g, int s) {
        this.s = s
        this.g = g;
        marked = new boolean[g.vSize()];
        onStack = new boolean[g.vSize()];
        findCycle(g,s);
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public void findCycle(Graph g, int v) {

        marked[v] = true;
        onStack[v] = true;

        for (int w : g.adjacentNodes(v)) {
            if(!marked[w]) {
                findCycle(g,w);
            } else if (onStack[w]) {
                hasCycle = true;
                return;
            }
        }

        onStack[v] = false;
    }
}
connect2krish
  • 159
  • 1
  • 2
  • 9
Ivan Voroshilin
  • 5,233
  • 3
  • 32
  • 61
  • I think there is a mistake in the ordered graph code. In case of traversing a graph in the shape of O, with the root on the top and with all the edges directed to bottom, this algorithm will detect cycle (firstly will traverse the left side of O to bottom and mark all nodes as marked, then the right part of O until I will get to bottom, which is already marked). I would add there marked[v]= false; just after findCycle(g,w); What do you think? – Matej Briškár Apr 06 '15 at 10:41
  • In `DFSCycle` the condition to check for cycle is incorrect. It should be `if(i != u) // If an adjacent is visited and not parent of current vertex, then there is a cycle. { hasCycle = true; return; }` – akhil_mittal Jan 14 '18 at 09:58
  • 2
    @IvanVoroshilin Shouldn't `} else if (v != u) {` be `} else if (w != u) {`? – Etherealone May 28 '18 at 17:28

5 Answers5

71

Answering my question:

The graph has a cycle if and only if there exists a back edge. A back edge is an edge that is from a node to itself (selfloop) or one of its ancestor in the tree produced by DFS forming a cycle.

Both approaches above actually mean the same. However, this method can be applied only to undirected graphs.

The reason why this algorithm doesn't work for directed graphs is that in a directed graph 2 different paths to the same vertex don't make a cycle. For example: A-->B, B-->C, A-->C - don't make a cycle whereas in undirected ones: A--B, B--C, C--A does.

Find a cycle in undirected graphs

An undirected graph has a cycle if and only if a depth-first search (DFS) finds an edge that points to an already-visited vertex (a back edge).

Find a cycle in directed graphs

In addition to visited vertices we need to keep track of vertices currently in recursion stack of function for DFS traversal. If we reach a vertex that is already in the recursion stack, then there is a cycle in the tree.

Update: Working code is in the question section above.

Ivan Voroshilin
  • 5,233
  • 3
  • 32
  • 61
  • 14
    In undirected graph there exists a cycle only if there is a back edge excluding the parent of the edge from where the back edge is found.Else every undirected graph has a cycle by default if we don't exclude the parent edge when finding a back edge. – crackerplace Jan 11 '15 at 16:51
  • DFS method to find cycle in directed graph so it runs in linear time. [Python3 implementation](https://github.com/seahrh/coding-interview/blob/42e480685c627f3c383bf2ba075d7f44692fae97/src/graphs/graph.py#L81) to return the first cycle found as a subgraph, [with tests](https://github.com/seahrh/coding-interview/blob/42e480685c627f3c383bf2ba075d7f44692fae97/src/graphs/tests/test_graph.py#L66). Based on 10-minute [video explainer](https://www.youtube.com/watch?v=rKQaZuoUR4M). – ruhong Jun 18 '20 at 10:38
  • "If we reach a vertex that is already in the recursion stack" I have a confusion for this part for finding a cycle in directed graphs. Your 1st line says in addition to visited vertices we have to consider recursion stack. But next line you said just about the recursion stack. Can you clarify the confusion? – avijit Aug 24 '20 at 05:32
11

For the sake of completion, it is possible to find cycles in a directed graph using DFS (from wikipedia):

 L ← Empty list that will contain the sorted nodes
while there are unmarked nodes do
    select an unmarked node n
    visit(n) 
function visit(node n)
    if n has a temporary mark then stop (not a DAG)
    if n is not marked (i.e. has not been visited yet) then
        mark n temporarily
        for each node m with an edge from n to m do
            visit(m)
        mark n permanently
        unmark n temporarily
        add n to head of L
Igor Zelaya
  • 4,167
  • 4
  • 35
  • 52
3

I think the above code works only for a connected digraph since we start dfs from the source node only, for if the digraph is not connected there may be a cycle in the other component which may go unnoticed!

  • I don't know why this comment get downvoted, but he did give an important point, if a digraph is not strongly connected, i.e., there exists some vertices that are not reachable from other vertices, a DFS search for a cycle might fail as the graph might be separated into multiple SCCs, refer https://stackoverflow.com/a/32893154/10661805 for finding cycles in a digraph. – Ray Jasson May 29 '20 at 17:00
1

Here is the code I've written in C based on DFS to find out whether a given undirected graph is connected/cyclic or not. with some sample output at the end. Hope it'll be helpful :)

#include<stdio.h>
#include<stdlib.h>

/****Global Variables****/
int A[20][20],visited[20],count=0,n;
int seq[20],connected=1,acyclic=1;

/****DFS Function Declaration****/
void DFS();

/****DFSearch Function Declaration****/
void DFSearch(int cur);

/****Main Function****/
int main() 
   {    
    int i,j;

    printf("\nEnter no of Vertices: ");
    scanf("%d",&n);

    printf("\nEnter the Adjacency Matrix(1/0):\n");
    for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
        scanf("%d",&A[i][j]);

    printf("\nThe Depth First Search Traversal:\n");

    DFS();

    for(i=1;i<=n;i++)
        printf("%c,%d\t",'a'+seq[i]-1,i);

    if(connected && acyclic)    printf("\n\nIt is a Connected, Acyclic Graph!");
    if(!connected && acyclic)   printf("\n\nIt is a Not-Connected, Acyclic Graph!");
    if(connected && !acyclic)   printf("\n\nGraph is a Connected, Cyclic Graph!");
    if(!connected && !acyclic)  printf("\n\nIt is a Not-Connected, Cyclic Graph!");

    printf("\n\n");
    return 0;
   }

/****DFS Function Definition****/
void DFS()
    { 
    int i;
    for(i=1;i<=n;i++)
        if(!visited[i])
          {
        if(i>1) connected=0;
        DFSearch(i);    
              } 
    }

/****DFSearch Function Definition****/
void DFSearch(int cur) 
    {
    int i,j;
    visited[cur]=++count;

        seq[count]=cur; 
        for(i=1;i<count-1;i++)
                if(A[cur][seq[i]]) 
                   acyclic=0;

    for(i=1;i<=n;i++)
        if(A[cur][i] && !visited[i])
           DFSearch(i);

    }

Sample Output:

majid@majid-K53SC:~/Desktop$ gcc BFS.c

majid@majid-K53SC:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 10

Enter the Adjacency Matrix(1/0):

0 0 1 1 1 0 0 0 0 0 
0 0 0 0 1 0 0 0 0 0 
0 0 0 1 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 1 0 0 1 0 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 0 0 0 0 1 
0 0 0 0 0 0 1 0 0 0 

The Depdth First Search Traversal:
a,1 c,2 d,3 f,4 b,5 e,6 g,7 h,8 i,9 j,10    

It is a Not-Connected, Cyclic Graph!


majid@majid-K53SC:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 4

Enter the Adjacency Matrix(1/0):
0 0 1 1
0 0 1 0
1 1 0 0
0 0 0 1

The Depth First Search Traversal:
a,1 c,2 b,3 d,4 

It is a Connected, Acyclic Graph!


majid@majid-K53SC:~/Desktop$ ./a.out
************************************

Enter no of Vertices: 5

Enter the Adjacency Matrix(1/0):
0 0 0 1 0
0 0 0 1 0
0 0 0 0 1
1 1 0 0 0 
0 0 1 0 0

The Depth First Search Traversal:
a,1 d,2 b,3 c,4 e,5 

It is a Not-Connected, Acyclic Graph!

*/
Majid NK
  • 311
  • 1
  • 3
  • 16
1

Offering an observation with regards to a subset of the problem here: a connected undirected graph will have a cycle if num_edges >= num_nodes.

UPDATE: Also offering Python code for detecting cycles in an undirected graph using DFS. Would greatly appreciate help in optimizing this.

def detect_cycle(graph, root):
    # Create an empty stack
    stack = []
    
    # Track visited nodes
    visited = [False] * graph.num_nodes

    # Track the order of traversal
    traversal = []

    # Track parents of traversed nodes
    parent = [None] * graph.num_nodes
    
    # Begin Traversal
    stack.append(root)
    parent[root] = root

    while len(stack) > 0:
        # Pop the stack
        node = stack.pop()
        if not visited[node]:
            visited[node] = True
            traversal.append(node)
            
            # Check the neighbors for visited nodes, or continue traversal
            for neighbor in graph.edges[node]:
                if not visited[neighbor]:
                    stack.append(neighbor)
                    parent[neighbor] = node
                
                # If the neighbor is visited, check for back edge 
                # If a back edge exists, neighbor will have been visited before 
                # the parent of the current node
                elif traversal.index(neighbor) < traversal.index(parent[node]):
                    return (f'Cycle at node {node}!')
                                    
    return ('Acyclic graph!')
vj_sparrow
  • 11
  • 3