Topological Sort Concepts
What is Topological Sort?
Topological sort produces a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge u→v, vertex u comes before v in the ordering.
DAG Example: Valid Topological Orders:
0 → 1 → 3 [0, 1, 2, 3, 4]
0 → 2 → 3 [0, 2, 1, 3, 4]
↓ [2, 0, 1, 3, 4]
4 (Multiple valid orderings possible)
Key Concepts
- Prerequisite: Edge u→v means u must come before v
- DAG: Directed Acyclic Graph (no cycles allowed)
- In-degree: Number of incoming edges to a vertex
- Source: Vertex with in-degree 0 (no prerequisites)
When to Use Topological Sort
| Problem Type | Example |
|---|---|
| Course prerequisites | Take Course 0 before Course 1 |
| Task scheduling | Build foundation before walls |
| Build systems | Compile dependencies |
| Spreadsheet formulas | Cell references |
| Package installation | npm/pip dependencies |
Two Main Approaches
- DFS-based: Use DFS to produce reverse post-order
- Kahn's Algorithm (BFS-based): Use in-degree and queue
Both run in O(V + E) time.
Topological Sort Implementations
Kahn's Algorithm (BFS-based)
import java.util.*;
public class TopologicalSort {
// Kahn's Algorithm
public int[] kahnTopoSort(int n, int[][] edges) {
// Build graph and compute in-degrees
Map<Integer, List<Integer>> graph = new HashMap<>();
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
inDegree[edge[1]]++;
}
// Add all sources (in-degree 0) to queue
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int[] result = new int[n];
int index = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
result[index++] = node;
for (int neighbor : graph.get(node)) {
inDegree[neighbor]--;
if (inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
// Check for cycle
if (index != n) {
return new int[]{}; // Cycle exists
}
return result;
}
// Returns empty array if cycle exists
// Returns topological order otherwise
}
DFS-based Topological Sort
public int[] dfsTopoSort(int n, int[][] edges) {
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
}
int[] color = new int[n]; // 0: unvisited, 1: visiting, 2: visited
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
if (color[i] == 0) {
if (dfsHelper(graph, i, color, stack)) {
return new int[]{}; // Cycle detected
}
}
}
int[] result = new int[n];
for (int i = 0; i < n; i++) {
result[i] = stack.pop();
}
return result;
}
private boolean dfsHelper(Map<Integer, List<Integer>> graph, int node,
int[] color, Stack<Integer> stack) {
color[node] = 1; // Mark as visiting
for (int neighbor : graph.get(node)) {
if (color[neighbor] == 1) {
return true; // Cycle detected
}
if (color[neighbor] == 0) {
if (dfsHelper(graph, neighbor, color, stack)) {
return true;
}
}
}
color[node] = 2; // Mark as visited
stack.push(node);
return false;
}
Course Schedule Problem
// Can finish all courses?
public boolean canFinish(int numCourses, int[][] prerequisites) {
return kahnTopoSort(numCourses, prerequisites).length > 0;
}
// Return course ordering
public int[] findOrder(int numCourses, int[][] prerequisites) {
return kahnTopoSort(numCourses, prerequisites);
}
Complexity Comparison
| Approach | Time | Space | Detects Cycles |
|---|---|---|---|
| Kahn's (BFS) | O(V + E) | O(V) | Yes (incomplete ordering) |
| DFS-based | O(V + E) | O(V) | Yes (back edge detection) |
Interview Tips
- Always check for cycles - if cycle exists, no valid ordering
- Kahn's is easier to implement and naturally detects cycles
- DFS gives reverse post-order - remember to reverse the result
- Multiple valid orderings - any valid order is acceptable
- Edge cases: single node, no edges, all edges in one direction
Practice Problems
There are a total of numCourses courses labeled 0 to numCourses-1. Some courses have prerequisites. Given prerequisites[i] = [ai, bi], you must take course bi before ai. Return true if you can finish all courses.
Example:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Take course 0 first, then course 1.
Optimal Solution — O(V + E) time, O(V + E) space
Kahn's algorithm: if result contains all courses, return true
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
for (int[] pre : prerequisites) {
graph.get(pre[1]).add(pre[0]);
inDegree[pre[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++)
if (inDegree[i] == 0) queue.offer(i);
int count = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
count++;
for (int next : graph.get(course)) {
if (--inDegree[next] == 0) queue.offer(next);
}
}
return count == numCourses;
}
}Edge Cases:
- No prerequisites: return true
- Single course: return true
- Cycle exists: return false
Return the ordering of courses to finish all courses. If impossible, return empty array.
Example:
Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: 0123
One valid ordering: 0 → 1 → 2 → 3.
Optimal Solution — O(V + E) time, O(V + E) space
Kahn's algorithm: store result order
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
for (int[] pre : prerequisites) {
graph.get(pre[1]).add(pre[0]);
inDegree[pre[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++)
if (inDegree[i] == 0) queue.offer(i);
int[] order = new int[numCourses];
int idx = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
order[idx++] = course;
for (int next : graph.get(course))
if (--inDegree[next] == 0) queue.offer(next);
}
return idx == numCourses ? order : new int[0];
}
}Edge Cases:
- No prerequisites
- Cycle exists: return empty
- Multiple valid orders
Quiz
1. Can topological sort be applied to a graph with cycles?
2. In Kahn's algorithm, what does it mean if we can't add all vertices to the result?
3. What is the primary purpose of Topological Sort?
4. What is a common mistake when implementing Topological Sort?
Flashcards
Question
What is topological sort and when can it be used?
Click to reveal answer
Answer
Topological sort produces a linear ordering of vertices in a DAG where for every edge u→v, u comes before v. It's used for dependency resolution, task scheduling, and course prerequisites.
Question
How does Kahn's algorithm detect cycles?
Click to reveal answer
Answer
Kahn's algorithm adds vertices with in-degree 0 to a queue. If after processing, not all vertices are in the result, the remaining vertices form a cycle (they always have non-zero in-degree).
Question
What is Topological Sort?
Click to reveal answer
Answer
Topological Sort is a key concept in software engineering.
Question
When to use Topological Sort?
Click to reveal answer
Answer
Use Topological Sort when building production systems that require reliability, scalability, and maintainability.
Question
Topological Sort best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.Topological sort only works on DAGs (Directed Acyclic Graphs)
- 2.Kahn's algorithm uses in-degree tracking and BFS
- 3.DFS-based uses reverse post-order detection
- 4.Cycle detection: Kahn's returns incomplete result, DFS finds back edge
- 5.Multiple valid orderings may exist - any valid one is acceptable
Interview Tips
- •Course Schedule problems are classic topological sort applications
- •Clarify the edge direction: [a,b] means b→a (b before a)
- •Kahn's is easier to implement and debug than DFS-based
- •Always check for cycles - if cycle, return empty/impossible
- •For finding ALL valid orderings, use DFS with backtracking
Cheat Sheet
Topological Sort Cheat Sheet
When to Use
- Dependency resolution (courses, build systems)
- Task ordering with prerequisites
- Detecting cycles in directed graphs
Kahn's Algorithm (BFS)
int[] inDegree = new int[n];
// Build graph, compute in-degrees
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
int[] result = new int[n];
int idx = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
result[idx++] = node;
for (int neighbor : graph.get(node)) {
if (--inDegree[neighbor] == 0) queue.offer(neighbor);
}
}
// If idx != n, cycle exists
DFS-based Topological Sort
// Reverse post-order gives topological sort
void dfs(int node) {
color[node] = 1;
for (int neighbor : graph.get(node)) {
if (color[neighbor] == 1) return true; // Cycle!
if (color[neighbor] == 0 && dfs(neighbor)) return true;
}
color[node] = 2;
stack.push(node);
}
Cycle Detection
- Kahn's: If result doesn't contain all vertices → cycle
- DFS: If back edge found (node in visiting state) → cycle
Complexity
- Time: O(V + E)
- Space: O(V)
Course Schedule Pattern
prerequisites[i] = [a, b]means b → a (b before a)- FindOrder: Kahn's algorithm on prerequisite graph
- CanFinish: Check if topological sort includes all courses