What is Topological Sort?
Topological Sort is a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge , vertex comes before vertex in the ordering. It is extensively used for scheduling tasks, resolving dependencies, and building systems (like npm, make, or maven).
Explanation
- Imagine you are enrolling in university courses. You cannot take
Calculus IIbefore you takeCalculus I. If you model courses as nodes and prerequisites as directed edges, a Topological Sort provides you with a valid sequence in which you can take the courses to satisfy all prerequisites.
The DAG Requirement
- Topological sort is impossible if the graph contains a cycle. If
ArequiresB, andBrequiresA, neither can be executed first. - Thus, it strictly applies only to Directed Acyclic Graphs (DAG). If a graph cannot be topologically sorted, it proves the graph has a cycle.
The Two Main Approaches
- There are two legendary algorithms to compute the topological sort:
- 1. Kahn’s Algorithm (BFS based): Uses an in-degree array (counting incoming edges) and a Queue. Nodes with 0 dependencies are pushed to the queue and processed.
- 2. DFS Algorithm: Uses Depth-First Search. As you completely finish exploring a node’s recursive branches, you push the node onto a Stack. Reversing the stack at the end yields the topological order.
How It Works
Approach 1: Kahn’s Algorithm (In-Degree)
- Calculate the in-degree (number of incoming edges) for every vertex.
- Push all vertices with an in-degree of
0into a Queue. - While the Queue is not empty:
- Pop vertex
U. AddUto the topological order array. - For every neighbor
VofU, decrementV’s in-degree by 1. - If
V’s in-degree hits0, pushVinto the Queue.
- Pop vertex
- If the final sorted array doesn’t contain all vertices, the graph has a cycle!
flowchart TD A["Calculate In-Degree for all nodes"] --> B["Queue = [Nodes with In-Degree 0]"] B --> C{"Is Queue empty?"} C -- No --> D["Pop U from Queue\nAdd U to result list"] D --> E["For each neighbor V of U"] E --> F["in_degree[V] -= 1"] F --> G{"in_degree[V] == 0?"} G -- Yes --> H["Push V to Queue"] G -- No --> E2["Next neighbor"] H --> E2 E2 -- Loop ends --> C C -- Yes --> I{"Result size == V?"} I -- Yes --> J["Return Result (Valid Sort)"] I -- No --> K["Cycle Detected! No Sort Possible"]
Approach 2: DFS Stack
- Create an empty Stack and a
visitedboolean array. - Loop through all vertices. If a vertex is unvisited, launch a
DFS(vertex). - Inside
DFS(u):- Mark
uas visited. - Recursively call DFS on all unvisited neighbors of
u. - Once all neighbors are processed, Push
uto the Stack.
- Mark
- After the loop finishes, popping everything off the stack yields the topological order.
Time & Space Complexity
-
Complexity Summary
- Time Complexity: O(V + E) — Both Kahn’s and DFS visit every vertex and edge exactly once.
- Space Complexity: O(V) — Memory for the Queue/Stack, In-Degree array, and Visited array.
Implementation
-
Implementation of Kahn's Algorithm (BFS based). Python · Cpp · Java Script · Java · C
Kahn’s algorithm is preferred because it handles cycle detection effortlessly. Languages:
from collections import deque
def topological_sort_kahn(num_vertices, graph):
"""
Kahn's Algorithm for Topological Sort
Time: O(V + E) | Space: O(V)
"""
in_degree = [0] * num_vertices
# Calculate in-degrees
for u in range(num_vertices):
for v in graph.get(u, []):
in_degree[v] += 1
# Queue for nodes with no incoming edges
queue = deque([i for i in range(num_vertices) if in_degree[i] == 0])
top_order = []
while queue:
u = queue.popleft()
top_order.append(u)
# Reduce in-degree of neighbors
for v in graph.get(u, []):
in_degree[v] -= 1
if in_degree[v] == 0:
queue.append(v)
# Cycle check
if len(top_order) != num_vertices:
return "Cycle detected! Topological sort impossible."
return top_order
# Example Setup
# 5 -> 2, 5 -> 0, 4 -> 0, 4 -> 1, 2 -> 3, 3 -> 1
graph = {
5: [2, 0],
4: [0, 1],
2: [3],
3: [1],
0: [],
1: []
}
print("Topological Order:", topological_sort_kahn(6, graph))
# Output: [4, 5, 2, 0, 3, 1] (or similar valid ordering)#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<int> topologicalSortKahn(int V, vector<vector<int>>& adj) {
vector<int> in_degree(V, 0);
for (int u = 0; u < V; u++) {
for (int v : adj[u]) {
in_degree[v]++;
}
}
queue<int> q;
for (int i = 0; i < V; i++) {
if (in_degree[i] == 0) q.push(i);
}
vector<int> top_order;
while (!q.empty()) {
int u = q.front();
q.pop();
top_order.push_back(u);
for (int v : adj[u]) {
if (--in_degree[v] == 0) {
q.push(v);
}
}
}
if (top_order.size() != V) {
cout << "Cycle detected!\n";
return {};
}
return top_order;
}
int main() {
int V = 6;
vector<vector<int>> adj(V);
adj[5] = {2, 0};
adj[4] = {0, 1};
adj[2] = {3};
adj[3] = {1};
vector<int> res = topologicalSortKahn(V, adj);
cout << "Topological Order: ";
for (int i : res) cout << i << " ";
cout << "\n";
return 0;
}function topologicalSortKahn(V, adj) {
let in_degree = Array(V).fill(0);
for (let u = 0; u < V; u++) {
for (let v of adj[u] || []) {
in_degree[v]++;
}
}
let queue = [];
for (let i = 0; i < V; i++) {
if (in_degree[i] === 0) queue.push(i);
}
let top_order = [];
while (queue.length > 0) {
let u = queue.shift();
top_order.push(u);
for (let v of adj[u] || []) {
in_degree[v]--;
if (in_degree[v] === 0) queue.push(v);
}
}
if (top_order.length !== V) return "Cycle detected!";
return top_order;
}
const adj = {
5: [2, 0],
4: [0, 1],
2: [3],
3: [1],
0: [],
1: []
};
console.log("Topological Order:", topologicalSortKahn(6, adj));import java.util.*;
public class TopologicalSort {
public static List<Integer> topologicalSortKahn(int V, List<List<Integer>> adj) {
int[] in_degree = new int[V];
for (int u = 0; u < V; u++) {
for (int v : adj.get(u)) {
in_degree[v]++;
}
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < V; i++) {
if (in_degree[i] == 0) {
queue.add(i);
}
}
List<Integer> top_order = new ArrayList<>();
while (!queue.isEmpty()) {
int u = queue.poll();
top_order.add(u);
for (int v : adj.get(u)) {
if (--in_degree[v] == 0) {
queue.add(v);
}
}
}
if (top_order.size() != V) {
System.out.println("Cycle detected!");
return new ArrayList<>();
}
return top_order;
}
public static void main(String[] args) {
int V = 6;
List<List<Integer>> adj = new ArrayList<>(V);
for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
adj.get(5).add(2);
adj.get(5).add(0);
adj.get(4).add(0);
adj.get(4).add(1);
adj.get(2).add(3);
adj.get(3).add(1);
System.out.println("Topological Order: " + topologicalSortKahn(V, adj));
}
}#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_V 100
void topologicalSortKahn(int V, int adj[MAX_V][MAX_V], int edge_counts[MAX_V]) {
int in_degree[MAX_V] = {0};
for (int u = 0; u < V; u++) {
for (int i = 0; i < edge_counts[u]; i++) {
in_degree[adj[u][i]]++;
}
}
int queue[MAX_V], front = 0, rear = 0;
for (int i = 0; i < V; i++) {
if (in_degree[i] == 0) {
queue[rear++] = i;
}
}
int top_order[MAX_V], count = 0;
while (front < rear) {
int u = queue[front++];
top_order[count++] = u;
for (int i = 0; i < edge_counts[u]; i++) {
int v = adj[u][i];
if (--in_degree[v] == 0) {
queue[rear++] = v;
}
}
}
if (count != V) {
printf("Cycle detected!\n");
return;
}
printf("Topological Order: ");
for (int i = 0; i < count; i++) {
printf("%d ", top_order[i]);
}
printf("\n");
}
int main() {
int V = 6;
int adj[MAX_V][MAX_V];
int edge_counts[MAX_V] = {0};
adj[5][edge_counts[5]++] = 2;
adj[5][edge_counts[5]++] = 0;
adj[4][edge_counts[4]++] = 0;
adj[4][edge_counts[4]++] = 1;
adj[2][edge_counts[2]++] = 3;
adj[3][edge_counts[3]++] = 1;
topologicalSortKahn(V, adj, edge_counts);
return 0;
}
When to Use Topological Sort
flowchart TD Q{"Goal"} Q -- "Find shortest path" --> R1["❌ Use Dijkstra or BFS"] Q -- "Graph is undirected" --> R2["❌ Cannot top-sort an undirected graph"] Q -- "Resolve build dependencies" --> R3["✅ Use Kahn's Algorithm\nPerfect for Package Managers"] Q -- "Detect cyclic dependencies" --> R4["✅ Use Kahn's Algorithm\nFails gracefully if cycle exists"]
✅ Use Topological Sort When
- Building compilers to determine the order of evaluating subexpressions.
- Task scheduling algorithms where task B can only start after task A finishes.
- Package managers (NPM, Pip) resolving complex nested dependency trees.
Key Takeaways
- Core idea — Valid execution ordering for directed graphs with no cycles.
- Kahn’s vs DFS — Kahn’s algorithm (using in-degrees) is generally easier to implement intuitively and detects cycles cleanly.
- Not Unique — A graph can have multiple valid topological sort sequences.
- DAG Mandatory — Undirected graphs or cyclic directed graphs mathematically cannot be sorted topologically.