Cycle Detection in Undirected Graph in DSA Python - Time & Space Complexity
We want to understand how the time needed to detect a cycle in an undirected graph changes as the graph grows.
Specifically, how does the number of nodes and edges affect the work done?
Analyze the time complexity of the following code snippet.
def dfs(node, parent, graph, visited):
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
if dfs(neighbor, node, graph, visited):
return True
elif neighbor != parent:
return True
return False
# graph is adjacency list, visited is list of booleans
for vertex in range(len(graph)):
if not visited[vertex]:
if dfs(vertex, -1, graph, visited):
print("Cycle detected")
break
This code uses depth-first search to check if any cycle exists in an undirected graph.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Visiting each node and exploring all its neighbors using DFS recursion.
- How many times: Each node is visited once, and each edge is checked twice (once from each end).
As the number of nodes (n) and edges (e) grow, the DFS explores all nodes and edges once.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 10 + 15*2 = 40 operations |
| 100 nodes, 200 edges | About 100 + 200*2 = 500 operations |
| 1000 nodes, 3000 edges | About 1000 + 3000*2 = 7000 operations |
Pattern observation: The work grows roughly in proportion to 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 in the graph.
[X] Wrong: "Cycle detection only depends on the number of nodes, so it is O(n)."
[OK] Correct: The edges connect nodes and must be checked too; ignoring edges misses part of the work and underestimates time.
Understanding this time complexity helps you explain how graph traversal scales and why DFS is efficient for cycle detection.
"What if we used a union-find data structure instead of DFS? How would the time complexity change?"
Practice
Solution
Step 1: Understand cycle meaning in undirected graph
A cycle is a path that starts and ends at the same vertex without repeating edges or vertices (except start/end).Step 2: Identify what cycle detection checks
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 vertex without repeating edges -> Option BQuick Check:
Cycle detection = find loops [OK]
- Confusing cycle with shortest path
- Thinking cycle counts vertices only
- Mixing cycle with vertex degree
Solution
Step 1: Identify purpose of visited tracking
We need to quickly check if a node was visited before to avoid revisiting and detect cycles.Step 2: Choose data structure with fast membership check
Set provides O(1) average time for membership checks, making it ideal for visited nodes.Final Answer:
Set -> Option AQuick Check:
Visited nodes stored in set [OK]
- Using list causes slow membership checks
- Using tuple which is immutable and not suitable
- Using dictionary unnecessarily when set suffices
graph = {0: [1, 2], 1: [0, 2], 2: [0, 1]}
visited = set()
def dfs(node, parent):
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
if dfs(neighbor, node):
return True
elif neighbor != parent:
return True
return False
print(dfs(0, -1))Solution
Step 1: Analyze graph structure
The graph has 3 nodes connected in a triangle: 0-1, 1-2, 2-0, which forms a cycle.Step 2: Trace DFS cycle detection
Starting at node 0, DFS visits 1, then 2. When 2 checks neighbors, it finds 0 visited and not parent, so returns True.Final Answer:
True -> Option AQuick Check:
Triangle graph has cycle = True [OK]
- Ignoring parent check causes false positives
- Assuming no cycle in small graphs
- Confusing return values in DFS
def dfs(node, parent, graph, visited):
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor, node, graph, visited)
elif neighbor != parent:
return True
return FalseSolution
Step 1: Check recursive call return usage
The recursive call dfs(neighbor, node, graph, visited) is made but its return value is ignored.Step 2: Understand impact on cycle detection
If a cycle is found deeper in recursion, it won't propagate True back up, so function may miss cycles.Final Answer:
Missing return statement in recursive dfs call -> Option DQuick Check:
Return recursive call result to detect cycles [OK]
- Ignoring return value of recursive calls
- Not passing visited set properly
- Confusing parent usage
graph = {
0: [1],
1: [0, 2],
2: [1, 3],
3: [2, 4, 5],
4: [3, 5],
5: [3, 4]
}
Which nodes form a cycle in this graph?Solution
Step 1: Analyze adjacency list for cycles
Nodes 3, 4, and 5 connect in a triangle: 3-4, 4-5, 5-3, forming a cycle.Step 2: Check other nodes for cycles
Nodes 0, 1, 2 form a simple chain without loops, so no cycle there.Final Answer:
Nodes 3, 4, 5 form a cycle -> Option CQuick Check:
Triangle subgraph = cycle [OK]
- Assuming chain is a cycle
- Ignoring cycle in subgraph
- Thinking all nodes must form cycle
Ready to prove it?
Pass the test and this pattern moves unknown
