A cycle in a graph is a sequence of edges and vertices where you can start from a specific vertex, follow a continuous path, and return to the same vertex without repeating any edge. In simpler terms, a cycle occurs when you can start at a vertex, traverse a set of edges, and return to the same vertex without retracing any edge.
Detecting cycles in a graph is crucial for several reasons. It helps identify potential problems like deadlocks in operating systems, ensures the reliability of dependency management in software development, and prevents infinite loops in network routing protocols.
Various algorithms have been developed to detect cycles in a graph, each with different approaches and efficiencies. Depth-First Search (DFS) is a common method that uses a recursive approach to explore all possible paths from a vertex and backtracks upon revisiting any vertex, thereby identifying cycles. Another popular algorithm is the Union-Find algorithm, which is effective in detecting cycles in undirected graphs by maintaining a disjoint-set data structure.
In this article we will look at DFS based algorithms to detect cycles in directed and un-directed graphs.
Detect Cycles in an Undirected Graph
Here’s the algorithm to detect cycles in an undirected graph:
- Perform a depth-first search (DFS) traversal starting from each unvisited vertex
- During DFS traversal, mark current vertex as visited
- For each unvisited neighbor from the current vertex:
- If the neighbor vertex is not visited, perform DFS on this vertex
- If the neighbor vertex is already visited, it means there is a cycle
Below is the implementation:
def dfs(vertex, graph, visited, parent):
"""
Perform a depth-first search to detect cycles in un-directed graph,
starting from a given node
Arguments:
vertex: the current vertex being visited
graph: the adjacency list representing the directed graph
visited: set of visited nodes
parent: the parent of the current node
Returns:
True if a cycle is detected, otherwise False.
"""
# Mark the current node as visited
visited.add(vertex)
# Traverse through all the neighbors of the current node
for neighbor in graph[vertex]:
# If the neighbor is not visited yet
if neighbor not in visited:
# Recursively call DFS on the neighbor
if dfs(neighbor, graph, visited, vertex):
return True
# If the neighbor is visited and not the parent of the current node
elif neighbor != parent:
# There is a cycle detected
return True
# No cycle detected for the current node
return False
def detect_cycle(graph):
"""
Function to detect if there is a cycle in the graph
Arguments:
graph: the adjacency list representing the undirected graph
Returns:
True if a cycle is detected, otherwise False.
"""
# Initialize a set to keep track of visited nodes
visited = set()
# Traverse through all the nodes in the graph
for node in graph:
# If the node is not visited yet
if node not in visited:
# Call the DFS function on the node
if dfs(node, graph, visited, None):
return True
# No cycle detected in the graph
return False
# Sample graph to test cycle detection
graph1 = {
'A' : ['B', 'C'],
'B' : ['A', 'C', 'D'],
'C' : ['A', 'B', 'E', 'F'],
'D' : ['B'],
'E' : ['C'],
'F' : ['C']
}
if detect_cycle(graph1):
print("Graph 1 has a cycle!")
else:
print("Graph 1 does not has a cycle!")
# Sample graph to test cycle detection
graph2 = {
'A' : ['B', 'C'],
'B' : ['A', 'D'],
'C' : ['A', 'E', 'F'],
'D' : ['B'],
'E' : ['C'],
'F' : ['C']
}
if detect_cycle(graph2):
print("Graph 2 has a cycle!")
else:
print("Graph 2 does not has a cycle!")
Detect Cycles in an directed Graph
Here’s an algorithm to detect cycles in a directed graph using the white, gray, and black (0, 1, 2) marking technique:
- Initialize all vertices in the graph as white (0).
- Perform a depth-first search (DFS) traversal starting from vertices still marked as white.
- During the DFS traversal, mark each vertex as follows:
- When a vertex is first visited, mark it as gray (1).
- When the DFS has finished exploring all the vertices reachable from the current vertex, mark it as black (2).
- If, during the DFS traversal, a gray vertex is encountered, it means a cycle has been detected.
Below is the implementation:
WHITE = 0
GRAY = 1
BLACK = 2
def dfs(vertex, graph, color_of_vertex):
"""
Perform a depth-first search to detect cycles in directed graph,
starting from a given vertex
Arguments:
vertex: the current vertex being visited
graph: the adjacency list representing the directed graph
color_of_vertex: dictonary containing the color of each vertex
Returns:
True if a cycle is detected, otherwise False.
"""
# Mark the current vertex as gray (in progress)
color_of_vertex[vertex] = GRAY
# Check all neighboring vertices
for neighbor in graph[vertex]:
if color_of_vertex[neighbor] == WHITE:
# If the neighbor hasn't been visited, explore it
if dfs(neighbor, graph, color_of_vertex) == True:
return True # Cycle detected in subtree
elif color_of_vertex[neighbor] == GRAY:
# If we find a gray neighbor, we've found a cycle
return True
# Mark the vertex as black
# This means that all vertices reachable from this node
# are fully explored before backtracking
color_of_vertex[vertex] = BLACK
# No cycle found in this subtree
return False
def detect_cycle(graph):
# Initialize all vertices as white (not visited)
color_of_vertex = {}
for vertex in graph:
color_of_vertex[vertex] = WHITE
# Check each vertex in the graph
for vertex in graph:
# If the vertex hasn't been visited, start a DFS
if color_of_vertex[vertex] == WHITE:
if dfs(vertex, graph, color_of_vertex) == True:
return True # Cycle detected
# If we've checked all vertices and found no cycles
return False
# Sample graph to test cycle detection
graph1 = {
'A' : ['C'],
'B' : ['A', 'D'],
'C' : ['B', 'D'],
'D' : [],
}
if detect_cycle(graph1):
print("Graph 1 has a cycle!")
else:
print("Graph 1 does not has a cycle!")
# Sample graph to test cycle detection
graph2 = {
'A' : ['B', 'C'],
'B' : ['D'],
'C' : ['B', 'D'],
'D' : [],
}
if detect_cycle(graph2):
print("Graph 2 has a cycle!")
else:
print("Graph 2 does not has a cycle!")