Detect Cycle in Undirected Graph in Python

In this problem, we will be given an undirected graph. Our task in this problem is to tell if the given has a cycle or not.

Let us see some illustrations to understand what a cycle in a graph looks like.

Example:

Input: N = 8, E = 8

Output: Yes

Explanation: The diagram shows that there is a cycle.

Approach - 1

In the first approach, we will use the Depth First Search or DFS algorithm to find the cycle in the graph.

The plan is to use the DFS algorithm for all the unvisited nodes of the adjacency list. We have to use the algorithm multiple times to ensure that the connected components are also traversed and checked if they contain a cycle or not. We will create an array that will store the record of whether the current node is already visited or not. Also, we need to keep a record of the parent node of the current node. For every neighbor of the current node, we will recursively call the function to detect the cycle only if that node is not visited. However, if that node is already visited, we will check if that node is the parent node of the current; then, it cannot be a cycle. But if the current node is not the parent node and still it is already visited, this means that there is a cycle in the graph. Let us understand this concept with an example:

Consider a Graph:

  • Now, when we start from 1, we will check the neighbors and call the function for the neighboring node 2. Also, the parent of 2 is 1. Now executing the function for 2, we will run a loop over neighboring nodes of 2. One of the neighboring nodes is 1. 1 is already visited; however, 1 is also the parent node, so it does not represent a cycle.
  • For node 8, it will be already visited when running a loop over the neighboring nodes of 4.
  • When we reach 7, whose parent node is 6, and check for the neighboring elements of 7, 8 is already visited. The parent node of 7 is not equal to 8; therefore, it represents a cycle. Hence, the program will return True.
  • We have to return this algorithm for each connected component of the graph.
  • We will follow these steps to solve the problem using DFS.
    • We will create a function, Cyclic(), which will take the adjacency list of the graph and the number of vertices present in the graph. We will create an array vis[] of length equal to the number of vertices. This array will be initialized as a zero array since, in the beginning, none of the nodes is visited.
    • In this function, we will run a for loop to run the detection function for all the components of the graph.
    • We will create another function, detect(), which will take the adjacency list, starting vertex, vis[], and the parent node of the given vertex.
      • Inside the function, the first step is to mark the vis[vertex] = 1.
      • Then, we will run a loop over the neighboring nodes of the given vertex.
      • We will check if the current neighboring node is not visited, and then we will call the detect function recursively for this node. If this function returns True for a call, it means there is a cycle; hence, we will return True.
      • If the current neighboring node is already visited, we will check if that node is different than the parent node of the current vertex. If the visited node is different than the parent node, that means there is a cycle in the graph. Hence, we will return True.
      • If the loop completes without returning True, then we will return False, Which means there is no cycle in the currently connected components of the graph.
    • The detect() function will be called for each component of the graph in the Cyclic() function. If no component has a cycle, it will return False.

Code

Output

The graph contains a cycle
The graph doesn't contain a cycle

Time Complexity: Since we are maintaining the visited node array, we are not calling the detect() function on a node more than once. Therefore, we are visiting every node and edge once. Therefore, the time complexity of the DFS algorithm is linear. The total time complexity is equal to the sum of vertices and edges, i.e., O(V+E), where V is the number of vertices and E is the number of edges.

Auxiliary Space: We have used space to store the visited array. Hence, the space complexity is O(V). There is space used for storing the recursion stack.

Approach - 2

In this approach, we will use the BFS algorithm to detect the cycle in the given graph.

In the Breadth First Search algorithm, we visit the nodes level-wise. Firstly, we complete visiting the nodes of a particular level, and then we move on to the next level. The BFS algorithm is implemented using the Queue data structure. The queue is used in BFS because a Queue follows the First-In-First-Out approach. Hence, we visit all the nodes of a common level since they will be the ones added first in the queue then the nodes of the next level.

The BFS here will be modified. As we saw in the earlier approach, the basic idea of detecting a cycle is to check if the node is already visited or not, and if it is already visited, it should not be the parent node of the current source node. Hence, we need to keep track of the parent node of the current source node. So, in the queue, we will not add just the node but a list containing the node and its corresponding parent node so as to check in each iteration if the visited node is different from the parent node.

We will follow these steps to solve this problem:

  • We have to create two data structures here; the first is the queue to perform the BFS traversal. The second is the array to mark if the node is visited or not.
  • We will initialize the visited array of length equal to the number of vertices and give the value 0, representing no node is visited.
  • We will start with the first node, which is the node at the 0th index.
  • We will mark vis[0] = 1. We need to add this node to the queue also. In the queue, we will add a list of the structure [<node>, <corresponding parent node>]. There is no parent node for the first node; therefore, we will add -1. So, the queue will be initialized as [[0, -1]].
  • Now, we will start the BFS traversal using the while loop.
    • In each iteration, we will pop the 0th node from the queue and store the node and its parent node.
    • Then, we will traverse the adjacency list for this node using a for loop.
      • In the for loop, we will check if the current neighboring node is already visited or not.
      • If this node is not visited, then we will mark it visited and add this node to the queue. Now, we need to add the parent node also; hence, we will add [i, n] where n is the node popped from the q in the current iteration.
      • However, if the node is visited, then we will check if this ith node is different from the parent of the node n.
      • If yes, then it means a cycle exists, and we will return True
    • If the while loop ends without breaking, then we will return False, i.e., there is no cycle.
    • We need to repeat this process for each component of the given graph. Hence, we will call the bfs() function for each non-visited node of the graph.

Below is the Python program for this approach.

Code

Output

The graph contains a cycle
The graph doesn't have a cycle

Time Complexity: We are using linear loops to traverse the graph. Since we are visiting each node of the adjacency list for once, only the number of iterations is equal to the sum of the vertex and edges. Therefore, the time complexity is O(V+E).

Space Complexity: We have used O(V) memory to store the visited array.