Topological Sorting
[!NOTE] You cannot put on your shoes before your socks. Topological Sorting is the algorithm for linearizing dependencies.
1. The Problem: Dependency Hell
In a Directed Acyclic Graph (DAG), we want to find a linear ordering of vertices such that for every edge u → v, vertex u comes before v in the ordering.
- Directed: Order matters.
- Acyclic: No cycles allowed. If A depends on B, and B depends on A, you’re stuck.
Applications
- Build Systems (Make, Maven, npm): Compiling libraries in order.
- Task Scheduling: Determining the sequence of jobs.
- Course Prerequisites: CS101 before CS102.
2. Kahn’s Algorithm (BFS based)
This algorithm uses the concept of In-Degree (number of incoming edges).
- Calculate In-Degree for all nodes.
- Add all nodes with
In-Degree == 0to a Queue (Ready set). - While Queue is not empty:
- Remove node u from Queue (Add to result).
- For each neighbor v of u:
- Decrease In-Degree of v by 1.
- If In-Degree of v becomes 0, add v to Queue.
3. Interactive: The Task Scheduler
Resolve the dependencies. Click the glowing nodes (In-Degree 0) to “Complete” them and unlock the next tasks.
4. Code Implementation: Kahn’s Algorithm
Java
public List<Integer> topologicalSort(int V, List<List<Integer>> adj) {
int[] inDegree = new int[V];
for (int u = 0; u < V; u++) {
for (int v : adj.get(u)) {
inDegree[v]++;
}
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < V; i++) {
if (inDegree[i] == 0) queue.add(i);
}
List<Integer> result = new ArrayList<>();
while (!queue.isEmpty()) {
int u = queue.poll();
result.add(u);
for (int v : adj.get(u)) {
inDegree[v]--;
if (inDegree[v] == 0) {
queue.add(v);
}
}
}
if (result.size() != V) {
throw new IllegalStateException("Graph has a cycle!");
}
return result;
}
Go
func TopologicalSort(V int, adj map[int][]int) ([]int, error) {
inDegree := make([]int, V)
for u := 0; u < V; u++ {
for _, v := range adj[u] {
inDegree[v]++
}
}
queue := []int{}
for i := 0; i < V; i++ {
if inDegree[i] == 0 {
queue = append(queue, i)
}
}
result := []int{}
for len(queue) > 0 {
u := queue[0]
queue = queue[1:]
result = append(result, u)
for _, v := range adj[u] {
inDegree[v]--
if inDegree[v] == 0 {
queue = append(queue, v)
}
}
}
if len(result) != V {
return nil, fmt.Errorf("Graph has a cycle")
}
return result, nil
}
5. Deep Dive Strategy Lab: Topological Sorting
Intuition Through Analogy
Think of this chapter like city route planning. The goal is not to memorize a fixed trick, but to repeatedly answer:
- What is the bottleneck?
- Which constraint dominates (time, memory, latency, correctness)?
- Which representation makes the bottleneck easier to eliminate?