educative.io

Can't understand why there needs to be separate stack and visited lists

Hello, I can’t seem to think of a graph example where we are using recursion to detect a cycle, and one where my_stack[node] is true but not visited[node]. I did not get this solution, please provide an example graph where that is applicable.

From my perspective, if visited[node] is true, assuming directed graph, it has to be a cycle, can’t understand why if visited[node] then return False. Please explain.


Course: https://www.educative.io/collection/10370001/5550095527313408
Lesson: https://www.educative.io/collection/page/10370001/5550095527313408/5883717249662976

Hi @Amit_Adiraju_Narasim,
The separate stack and visited lists serve different purposes in the cycle detection algorithm:

  1. Visited List (visited):
  • The visited list keeps track of nodes that have been visited during the entire algorithm execution.
  • It helps prevent infinite recursion by ensuring that the algorithm does not revisit nodes that have already been explored.
  • If a node is visited in a previous recursive call but is not currently on the call stack, it means there is no cycle involving that node.
  • This list helps avoid redundant work by preventing revisits to nodes that have already been explored.
  1. Stack (my_stack):
  • The my_stack list maintains a record of nodes that are part of the current recursion stack.
  • It helps detect cycles by identifying nodes that are encountered again within the same recursion call.
  • If a node is encountered again while it is already present on the current recursion stack, it indicates the presence of a cycle.
  • This stack keeps track of nodes currently being explored in the depth-first search (DFS) traversal.

Separating these lists allows the algorithm to differentiate between nodes that have been visited in previous iterations and nodes that are currently part of the recursion stack. This distinction is crucial for accurately detecting cycles in a graph.

let’s look at a simple example of a graph to illustrate how the provided solution works. We’ll create a directed graph with some nodes and edges, and then apply the cycle detection algorithm on it.

Consider the following graph:

       0 <--- 3
       |     / ^
       |   /   |
       v /     |
       1 ---> 2

In this graph:

  • Node 0 has an edge to node 1.
  • Node 1 has an edge to nodes 2 and 3.
  • Node 2 has an edge back to node 1.
  • Node 3 has an edge back to node 0.

Now, let’s apply the cycle detection algorithm on this graph:

from collections import defaultdict

class Graph:
    def __init__(self, vertices):
        self.V = vertices
        self.graph = defaultdict(list)

    def add_edge(self, u, v):
        self.graph[u].append(v)

def detect_cycle(graph):
    visited = [False] * graph.V
    my_stack = [False] * graph.V

    for node in range(graph.V):
        if detect_cycle_recursive(graph, node, visited, my_stack):
            return True

    return False

def detect_cycle_recursive(graph, node, visited, my_stack):
    if my_stack[node]:
        return True

    if visited[node]:
        return False

    visited[node] = True
    my_stack[node] = True

    for adjacent in graph.graph[node]:
        if detect_cycle_recursive(graph, adjacent, visited, my_stack):
            return True

    my_stack[node] = False
    return False

if __name__ == "__main__":
    g = Graph(4)
    g.add_edge(0, 1)
    g.add_edge(1, 2)
    g.add_edge(1, 3)
    g.add_edge(2, 1)
    g.add_edge(3, 0)

    print("Does the graph contain a cycle?", detect_cycle(g))

When we run this code with the given graph, it will detect the presence of a cycle and return True. This is because there is a cycle in the graph: 0 → 1 → 2 → 1, which forms a cycle.

1 Like

Thank you for detailed explanation @Komal_Arif ! Still little confused on the reasoning for “my_stack” variable …

From this example 0 → 1 → 2 → 1 corresponds a cycle, wouldn’t we be able to detect a cycle without using “my_stack” variable , but just with visited dictionary ?

if visited[node] is true, return true ? Assuming graph is still directed ?

Why do we need a special “my_stack” to hold recursion stack nodes ? Efficiency over just visited dictionary ?

You raise a valid point, and in many cycle detection algorithms, the visited dictionary alone might suffice. However, in this specific algorithm, the my_stack variable serves an additional purpose that enhances the efficiency and accuracy of cycle detection.

While it’s true that using only the visited dictionary could potentially detect cycles, the additional my_stack variable allows for a more precise identification of cycles within the recursion stack. Here’s why my_stack is beneficial:

  1. Detecting Back Edges:
  • The my_stack variable helps identify back edges within the current recursion call stack. Back edges are edges that lead back to an ancestor node in the DFS traversal, indicating the presence of a cycle.
  • By tracking nodes currently on the recursion stack, the algorithm can detect when a node is encountered again while it is still on the stack. This scenario indicates the presence of a back edge and hence a cycle.
  1. Preventing Redundant Work:
  • The my_stack variable prevents redundant work by specifically identifying nodes that are part of the current recursion call stack. This distinction helps avoid revisiting nodes that are already being explored in the current DFS traversal, improving efficiency.
  1. Handling Directed Graphs:
  • In the case of directed graphs, cycles may involve revisiting nodes multiple times during different recursion calls. The my_stack variable helps differentiate between nodes visited in different recursive calls versus nodes visited within the same call, providing a more accurate detection of cycles.

While it’s theoretically possible to detect cycles using only the visited dictionary, incorporating the my_stack variable enhances the efficiency and accuracy of cycle detection, especially in scenarios involving back edges and directed graphs. This approach ensures that the algorithm can precisely identify cycles within the graph while minimizing redundant exploration of nodes.