Topological Sort Using DFS in DSA Python - Time & Space Complexity
We want to understand how the time needed to perform topological sort using DFS changes as the graph size grows.
Specifically, how the number of nodes and edges affects the work done.
Analyze the time complexity of the following code snippet.
def dfs(node, graph, visited, stack):
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
dfs(neighbor, graph, visited, stack)
stack.append(node)
def topological_sort(graph):
visited = {node: False for node in graph}
stack = []
for node in graph:
if not visited[node]:
dfs(node, graph, visited, stack)
return stack[::-1]
This code performs a topological sort on a directed graph using DFS traversal.
- Primary operation: DFS visits each node and explores all its neighbors.
- How many times: Each node is visited once, and each edge is explored once.
As the number of nodes (n) and edges (e) increase, the work grows roughly proportional to their sum.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 operations (10 + 15) |
| 100 nodes, 200 edges | About 300 operations (100 + 200) |
| 1000 nodes, 5000 edges | About 6000 operations (1000 + 5000) |
Pattern observation: The operations grow linearly with the total number of nodes plus edges.
Time Complexity: O(n + e)
This means the time needed grows linearly with the number of nodes plus edges in the graph.
[X] Wrong: "DFS visits each node multiple times, so complexity is higher than linear."
[OK] Correct: Each node is marked visited and processed only once, so DFS does not repeat work on the same node.
Understanding this time complexity helps you explain how graph traversal scales and why DFS-based topological sort is efficient.
"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 DFS role in Topological Sort
DFS visits nodes deeply, ensuring all nodes reachable from a node are visited before the node itself.Step 2: Relate DFS order to dependencies
In Topological Sort, this means all dependencies (nodes with edges to current node) are processed before the node, preserving order.Final Answer:
To visit nodes in a way that all dependencies come before the node itself -> Option AQuick Check:
DFS ensures dependency order in Topological Sort [OK]
- Confusing DFS with BFS for topological order
- Thinking DFS finds shortest paths here
- Mixing cycle detection with topological sorting
Solution
Step 1: Understand visited data structure type
Usually, visited is a list or dictionary indexed by node, so marking visited means assigning True to that index.Step 2: Identify correct syntax for assignment
Using visited[node] = True correctly marks the node as visited. Using '==' is a comparison, not assignment.Final Answer:
visited[node] = True -> Option CQuick Check:
Assign True to visited[node] to mark visited [OK]
- Using '==' instead of '=' for assignment
- Using visited.append(node) when visited is a dict/list
- Using visited.add(node) when visited is not a set
graph = {0: [1, 2], 1: [2], 2: []}
visited = [False, False, False]
stack = []
def dfs(node):
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
dfs(neighbor)
stack.append(node)
for v in range(3):
if not visited[v]:
dfs(v)
print(stack[::-1])Solution
Step 1: Trace DFS calls and stack appends
Start with node 0: visit 0, then neighbors 1 and 2. DFS(1) visits 1, then 2. DFS(2) visits 2, appends 2. Then append 1, then append 0.Step 2: Reverse stack to get topological order
Stack after DFS: [2, 1, 0]. Reversing gives [0, 1, 2].Final Answer:
[0, 1, 2] -> Option AQuick Check:
Stack reversed gives correct topological order [OK]
- Not reversing the stack before printing
- Appending nodes before recursive DFS calls
- Confusing order of neighbors visited
def dfs(node):
visited[node] = True
for neighbor in graph[node]:
if visited[neighbor]:
dfs(neighbor)
stack.append(node)Solution
Step 1: Analyze the if condition in DFS
The code calls dfs(neighbor) only if visited[neighbor] is True, which means neighbor was already visited. This causes repeated visits or infinite recursion.Step 2: Correct the condition to avoid revisiting
The condition should be 'if not visited[neighbor]' to visit unvisited neighbors only.Final Answer:
The condition should be 'if not visited[neighbor]' before calling dfs -> Option DQuick Check:
Visit neighbors only if not visited [OK]
- Calling dfs on already visited nodes
- Not marking nodes visited before recursion
- Appending nodes to stack too early
Solution
Step 1: Understand dependencies from edges
Edges mean 5 before 2 and 0, 4 before 0 and 1, 2 before 3, 3 before 1. So 5 and 4 come before 2,3,1,0.Step 2: Check each option for order validity
[5, 4, 2, 3, 1, 0] respects all edges: 5 before 2 and 0, 4 before 0 and 1, 2 before 3, 3 before 1. Others violate these orders.Final Answer:
[5, 4, 2, 3, 1, 0] -> Option BQuick Check:
All edges respected in order [OK]
- Ignoring edge directions in order
- Assuming any order of nodes is valid
- Not verifying all dependencies before nodes
Ready to prove it?
Pass the test and this pattern moves unknown
