Topological Sort Using Kahn's Algorithm BFS in DSA Python - Time & Space Complexity
We want to understand how the time needed to perform topological sort using Kahn's Algorithm grows as the graph size increases.
Specifically, how the number of nodes and edges affects the work done.
Analyze the time complexity of the following code snippet.
from collections import deque
def kahn_topological_sort(graph, indegree):
queue = deque()
for node in indegree:
if indegree[node] == 0:
queue.append(node)
topo_order = []
while queue:
current = queue.popleft()
topo_order.append(current)
for neighbor in graph[current]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return topo_order
This code performs topological sorting on a directed graph using Kahn's Algorithm with BFS.
- Primary operation: Visiting each node once and checking all its outgoing edges.
- How many times: Each node is enqueued and dequeued once; each edge is checked once when reducing indegree.
As the number of nodes (n) and edges (e) grow, the algorithm visits each node and edge once.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | ~25 operations (10 nodes + 15 edges) |
| 100 nodes, 200 edges | ~300 operations (100 + 200) |
| 1000 nodes, 5000 edges | ~6000 operations (1000 + 5000) |
Pattern observation: The work grows roughly in proportion to the sum of nodes and edges.
Time Complexity: O(n + e)
This means the time needed grows linearly with the number of nodes plus the number of edges in the graph.
[X] Wrong: "The algorithm takes O(n²) time because it has nested loops."
[OK] Correct: The inner loop runs over edges of each node, so all edges are visited once total, not for every node. So it is linear in edges, not quadratic.
Understanding this time complexity helps you explain how efficient topological sorting is on large graphs, a common topic in interviews.
"What if the graph was represented using an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand Kahn's Algorithm goal
Kahn's Algorithm is designed to produce a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge u -> v, u comes before v in the ordering.Step 2: Compare with other graph algorithms
Other options describe different algorithms: cycle detection, shortest path, and minimum spanning tree are unrelated to Kahn's Algorithm.Final Answer:
To find a topological ordering of a Directed Acyclic Graph (DAG) -> Option AQuick Check:
Topological sort = Kahn's Algorithm purpose [OK]
- Confusing topological sort with cycle detection
- Thinking it works on undirected graphs
- Mixing it up with shortest path algorithms
graph with n nodes?Solution
Step 1: Understand in-degree calculation
In-degree of a node is the count of edges coming into it. For each edge u -> v, increment in_degree[v].Step 2: Analyze code snippets
in_degree = [0] * n for u in range(n): for v in graph[u]: in_degree[v] += 1 correctly loops over each node u and its neighbors v, incrementing in_degree[v]. in_degree = [0] * n for v in range(n): for u in graph[v]: in_degree[v] += 1 increments the source node incorrectly. in_degree = [1] * n for u in range(n): for v in graph[u]: in_degree[v] += 1 initializes in_degree with 1s, which is wrong. in_degree = [0] * n for u in graph: for v in graph[u]: in_degree[u] += 1 increments in_degree[u], which is incorrect.Final Answer:
in_degree = [0] * n for u in range(n): for v in graph[u]: in_degree[v] += 1 -> Option AQuick Check:
Increment in_degree of destination node [OK]
- Incrementing in-degree of source node instead of target
- Initializing in-degree with wrong default values
- Swapping loop variables u and v
from collections import deque
graph = {0: [1, 2], 1: [3], 2: [3], 3: []}
n = 4
in_degree = [0]*n
for u in range(n):
for v in graph[u]:
in_degree[v] += 1
queue = deque([i for i in range(n) if in_degree[i] == 0])
topo_order = []
while queue:
node = queue.popleft()
topo_order.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
print(topo_order)Solution
Step 1: Calculate in-degree and initialize queue
In-degree: 0=0, 1=1, 2=1, 3=2. Queue starts with nodes having in-degree 0: [0].Step 2: Process nodes in queue
Pop 0, append to topo_order: [0]. Decrement in-degree of 1 and 2 to 0, add both to queue: [1, 2]. Pop 1, append: [0,1]. Decrement in-degree of 3 to 1. Pop 2, append: [0,1,2]. Decrement in-degree of 3 to 0, add 3 to queue: [3]. Pop 3, append: [0,1,2,3].Final Answer:
[0, 2, 1, 3] -> Option CQuick Check:
Topological order = [0, 2, 1, 3] [OK]
- Ignoring order of nodes with zero in-degree
- Not updating in-degree after popping nodes
- Appending nodes before in-degree reaches zero
from collections import deque
graph = {0: [1], 1: [2], 2: [0]}
n = 3
in_degree = [0]*n
for u in range(n):
for v in graph[u]:
in_degree[u] += 1
queue = deque([i for i in range(n) if in_degree[i] == 0])
topo_order = []
while queue:
node = queue.popleft()
topo_order.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
print(topo_order)Solution
Step 1: Identify incorrect in-degree update
The code increments in_degree[u] for edges u->v, but it should increment in_degree[v] because in-degree counts incoming edges to v.Step 2: Understand impact on queue initialization
Incorrect in-degree causes wrong zero in-degree nodes, so queue may be empty or incorrect, breaking the algorithm.Final Answer:
Change 'in_degree[u] += 1' to 'in_degree[v] += 1' to correctly count incoming edges -> Option BQuick Check:
Increment in-degree of target node, not source [OK]
- Incrementing in-degree of source node
- Not detecting cycles due to wrong in-degree
- Using wrong data structure for queue
Solution
Step 1: Calculate in-degree for each node
In-degree: 0=2 (from 5,4), 1=2 (from 4,3), 2=1 (from 5), 3=1 (from 2), 4=0, 5=0. Start queue with nodes 4 and 5.Step 2: Process nodes in queue and build order
Pop 4: order=[4], decrement in-degree of 0 to 1 and 1 to 1. Pop 5: order=[4,5], assuming graph[5]=[0,2], decrement 0 to 0 and append 0, decrement 2 to 0 and append 2: queue=[0,2]. Pop 0: order=[4,5,0]. Pop 2: order=[4,5,0,2], decrement 3 to 0, append 3. Pop 3: order=[4,5,0,2,3], decrement 1 to 0, append 1. Pop 1: order=[4,5,0,2,3,1].Final Answer:
[4, 5, 0, 2, 3, 1] -> Option DQuick Check:
Valid topological order respects edges [OK]
- Ignoring in-degree counts leading to invalid order
- Placing nodes before their dependencies
- Assuming any order is valid without checking edges
Ready to prove it?
Pass the test and this pattern moves unknown
