Topological Sort Using Kahn's Algorithm BFS in DSA Go - Time & Space Complexity
We want to understand how the time needed to perform topological sorting 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.
func kahnTopoSort(graph map[int][]int, indegree map[int]int) []int {
var queue []int
var result []int
for node, deg := range indegree {
if deg == 0 {
queue = append(queue, node)
}
}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
result = append(result, current)
for _, neighbor := range graph[current] {
indegree[neighbor]--
if indegree[neighbor] == 0 {
queue = append(queue, neighbor)
}
}
}
return result
}
This code performs a topological sort on a directed graph using Kahn's Algorithm with BFS.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Visiting each node once and processing 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 processes each node and edge once.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 operations (10 nodes + 15 edges) |
| 100 nodes, 200 edges | About 300 operations (100 + 200) |
| 1000 nodes, 5000 edges | About 6000 operations (1000 + 5000) |
Pattern observation: The work grows roughly in direct 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 uses nested loops."
[OK] Correct: Although there are loops inside loops, each edge and node is processed only once, so the total work is linear, not quadratic.
Understanding this time complexity helps you explain why Kahn's Algorithm is efficient for scheduling and dependency resolution problems.
"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 like cycle detection, shortest path, or minimum spanning tree solve different problems unrelated to topological sorting.Final Answer:
To find a topological ordering of a Directed Acyclic Graph (DAG) -> Option DQuick Check:
Topological sort = Kahn's Algorithm purpose [OK]
- Confusing topological sort with cycle detection
- Thinking it works on undirected graphs
- Assuming it finds shortest paths
Solution
Step 1: Identify queue operations needed
Kahn's Algorithm requires efficient enqueue (push back) and dequeue (pop front) operations for nodes with zero indegree.Step 2: Evaluate Go data structures
Slices can be used but dequeue from front is costly (O(n)). Maps don't maintain order. The container/list package provides a doubly linked list with O(1) enqueue and dequeue, ideal for queue.Final Answer:
Linked list from container/list package -> Option AQuick Check:
Efficient queue = container/list linked list [OK]
- Using slice and removing front element inefficiently
- Using map which is unordered
- Using fixed-size array without resizing
package main
import (
"container/list"
"fmt"
)
func kahnTopoSort(vertices int, edges [][2]int) []int {
indegree := make([]int, vertices)
graph := make([][]int, vertices)
for _, e := range edges {
graph[e[0]] = append(graph[e[0]], e[1])
indegree[e[1]]++
}
queue := list.New()
for i := 0; i < vertices; i++ {
if indegree[i] == 0 {
queue.PushBack(i)
}
}
var result []int
for queue.Len() > 0 {
front := queue.Remove(queue.Front()).(int)
result = append(result, front)
for _, nbr := range graph[front] {
indegree[nbr]--
if indegree[nbr] == 0 {
queue.PushBack(nbr)
}
}
}
return result
}
func main() {
edges := [][2]int{{5,0},{5,2},{4,0},{4,1},{2,3},{3,1}}
order := kahnTopoSort(6, edges)
fmt.Println(order)
}Solution
Step 1: Calculate indegrees and initial queue
Vertices: 0-5. Indegrees: 0->2 (from 4,5), 1->2 (from 3,4), 2->1 (from 5), 3->1 (from 2), 4->0, 5->0. Initial queue: nodes with indegree 0 are 4 and 5.Step 2: Process queue in FIFO order
Queue: [4,5]. Remove 4: result=[4], decrease indegree of 0 and 1. Indegree 0=1, 1=1. Remove 5: result=[4,5], decrease indegree of 0 and 2. Indegree 0=0 (enqueue 0), 2=0 (enqueue 2). Queue: [0,2]. Remove 0: result=[4,5,0], no neighbors. Remove 2: result=[4,5,0,2], decrease indegree of 3 to 0 (enqueue 3). Queue: [3]. Remove 3: result=[4,5,0,2,3], decrease indegree of 1 to 0 (enqueue 1). Queue: [1]. Remove 1: result=[4,5,0,2,3,1].Final Answer:
[4 5 0 2 3 1] -> Option AQuick Check:
Topological order = [4 5 0 2 3 1] [OK]
- Mixing order of initial zero indegree nodes
- Forgetting to enqueue neighbors when indegree hits zero
- Confusing DFS topological sort with Kahn's BFS
func kahnTopoSort(vertices int, edges [][2]int) []int {
indegree := make([]int, vertices)
graph := make([][]int, vertices)
for _, e := range edges {
graph[e[0]] = append(graph[e[0]], e[1])
indegree[e[1]]++
}
queue := list.New()
for i := 0; i < vertices; i++ {
if indegree[i] == 0 {
queue.PushBack(i)
}
}
var result []int
for queue.Len() > 0 {
front := queue.Front().Value.(int)
result = append(result, front)
for _, nbr := range graph[front] {
indegree[nbr]--
if indegree[nbr] == 0 {
queue.PushBack(nbr)
}
}
}
return result
}Solution
Step 1: Analyze queue usage in loop
The code uses queue.Front().Value to get the front element but never removes it from the queue, so the queue length never decreases, causing an infinite loop.Step 2: Identify missing operation
To fix, call queue.Remove(queue.Front()) to dequeue the front element after processing it.Final Answer:
Missing queue.Remove call to dequeue the front element -> Option CQuick Check:
Dequeue missing = infinite loop [OK]
- Forgetting to remove front element from queue
- Assuming indegree or graph initialization causes loop
- Not appending result correctly
Solution
Step 1: Understand Kahn's Algorithm cycle detection
Kahn's Algorithm processes nodes with zero indegree. If a cycle exists, some nodes never reach zero indegree and remain unprocessed.Step 2: Use processed node count to detect cycle
Count how many nodes are processed. If count is less than total vertices, it means some nodes are stuck in a cycle and were never enqueued.Final Answer:
Count processed nodes; if less than total vertices, a cycle exists -> Option BQuick Check:
Processed nodes < total vertices = cycle [OK]
- Assuming presence of zero indegree nodes means no cycle
- Confusing BFS with DFS cycle detection
- Thinking empty queue means acyclic always
Ready to prove it?
Pass the test and this pattern moves unknown
