Cycle Detection in Directed Graph in DSA Python - Time & Space Complexity
We want to understand how long it takes to check if a directed graph has a cycle.
Specifically, how the time grows as the graph gets bigger.
Analyze the time complexity of the following code snippet.
def dfs(node):
visited[node] = True
recursion_stack[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
if dfs(neighbor):
return True
elif recursion_stack[neighbor]:
return True
recursion_stack[node] = False
return False
for node in range(num_nodes):
if not visited[node]:
if dfs(node):
print("Cycle detected")
break
This code uses depth-first search to detect cycles by tracking nodes in the current path.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The depth-first search (DFS) visits each node and its neighbors.
- How many times: Each node and edge is visited once during DFS.
As the number of nodes and edges grows, the DFS explores more connections.
| Input Size (n nodes, e edges) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 visits (nodes + edges) |
| 100 nodes, 200 edges | About 300 visits |
| 1000 nodes, 5000 edges | About 6000 visits |
Pattern observation: The work grows roughly with the sum of nodes and edges.
Time Complexity: O(n + e)
This means the time to detect a cycle grows linearly with the number of nodes plus edges.
[X] Wrong: "Cycle detection takes O(n²) because of nested loops."
[OK] Correct: DFS visits each node and edge only once, so it does not repeatedly check all pairs.
Understanding this time complexity helps you explain efficient graph traversal in interviews confidently.
"What if the graph was represented as an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand cycle in directed graph
A cycle means starting from a node and following directed edges, you can return to the same node.Step 2: Identify what cycle detection finds
Cycle detection checks if such a path exists in the graph.Final Answer:
Whether there is a path that starts and ends at the same node following the direction of edges -> Option DQuick Check:
Cycle detection = path returning to start node [OK]
- Confusing cycle with shortest path
- Ignoring edge directions
- Thinking cycle means any repeated node without direction
Solution
Step 1: Identify recursion stack tracking
During DFS, nodes currently being explored are tracked to detect cycles.Step 2: Choose data structure for fast membership check
Set allows O(1) check if a node is in recursion stack.Final Answer:
Set -> Option AQuick Check:
Recursion stack uses set for fast lookup [OK]
- Using list which is slower for membership check
- Using queue which is FIFO, not suitable here
- Using dictionary without keys for membership
graph = {0: [1], 1: [2], 2: [0]}
visited = set()
rec_stack = set()
def dfs(node):
if node in rec_stack:
return True
if node in visited:
return False
visited.add(node)
rec_stack.add(node)
for neighbor in graph.get(node, []):
if dfs(neighbor):
return True
rec_stack.remove(node)
return False
print(dfs(0))Solution
Step 1: Trace DFS calls starting from node 0
Node 0 calls dfs(1), which calls dfs(2), which calls dfs(0) again.Step 2: Detect cycle when node 0 is in recursion stack
When dfs(0) is called second time, node 0 is in rec_stack, so returns True.Final Answer:
True -> Option AQuick Check:
Cycle detected = True [OK]
- Ignoring recursion stack check
- Confusing visited with recursion stack
- Assuming no cycle returns True
def dfs(node, graph, visited, rec_stack):
visited.add(node)
rec_stack.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor, graph, visited, rec_stack)
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return FalseSolution
Step 1: Check recursive call return usage
The recursive call dfs(neighbor, ...) is made but its return value is ignored.Step 2: Fix by returning True if recursive call finds cycle
Should return True immediately if dfs(neighbor, ...) returns True.Final Answer:
Missing return value when dfs(neighbor) is called recursively -> Option BQuick Check:
Return True on recursive cycle detection [OK]
- Ignoring recursive call return value
- Using wrong data structure for rec_stack
- Clearing visited incorrectly
graph = {
'A': ['B'],
'B': ['C', 'D'],
'C': ['E'],
'D': ['F'],
'E': ['B'],
'F': []
}
Which nodes are part of at least one cycle?Solution
Step 1: Identify cycles by tracing edges
Path B -> C -> E -> B forms a cycle involving B, C, E.Step 2: Check other nodes
A leads to B but is not in cycle; D and F have no cycles.Final Answer:
B, C, E -> Option CQuick Check:
Cycle nodes = B, C, E [OK]
- Including nodes only leading to cycle but not in it
- Ignoring indirect cycles
- Assuming all connected nodes form cycles
Ready to prove it?
Pass the test and this pattern moves unknown
