- The DSA Woodshed
- Algorithms
- Graphs
- Topological Sort
Topological Sort
Problem
Given a DAG represented as an adjacency list, return a valid topological ordering of its vertices. If the graph has a cycle, return an empty list.
Approach
- Kahn's algorithm (BFS): Track in-degrees; repeatedly dequeue nodes with in-degree 0 and decrement neighbors' in-degrees.
- DFS-based: Post-order DFS; reverse the finish order.
When to Use
Dependency resolution — "build order", "task scheduling with prereqs", "compile order". Any DAG where you need a valid linear ordering. Also: package managers, makefile targets, course planning.
Complexity
| Time | O(V + E) |
| Space | O(V + E) |
Source
"""Topological ordering of a directed acyclic graph (DAG).
Problem:
Given a DAG represented as an adjacency list, return a valid
topological ordering of its vertices. If the graph has a cycle,
return an empty list.
Approach:
1. Kahn's algorithm (BFS): Track in-degrees; repeatedly dequeue
nodes with in-degree 0 and decrement neighbors' in-degrees.
2. DFS-based: Post-order DFS; reverse the finish order.
When to use:
Dependency resolution — "build order", "task scheduling with prereqs",
"compile order". Any DAG where you need a valid linear ordering.
Also: package managers, makefile targets, course planning.
Complexity:
Time: O(V + E)
Space: O(V + E)
"""
from collections import deque
def topological_sort_kahn(
num_nodes: int,
edges: list[tuple[int, int]],
) -> list[int]:
"""Return a topological ordering using Kahn's algorithm.
*edges* is a list of (u, v) meaning u -> v.
Returns [] if a cycle exists.
>>> topological_sort_kahn(4, [(0, 1), (0, 2), (1, 3), (2, 3)])
[0, 1, 2, 3]
"""
adj: list[list[int]] = [[] for _ in range(num_nodes)]
in_degree = [0] * num_nodes
for u, v in edges:
adj[u].append(v)
in_degree[v] += 1
queue: deque[int] = deque(i for i in range(num_nodes) if in_degree[i] == 0)
order: list[int] = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in adj[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(order) != num_nodes:
return []
return order
def topological_sort_dfs(
num_nodes: int,
edges: list[tuple[int, int]],
) -> list[int]:
"""Return a topological ordering using DFS post-order reversal.
Returns [] if a cycle exists.
>>> topological_sort_dfs(4, [(0, 1), (0, 2), (1, 3), (2, 3)])
[0, 2, 1, 3]
"""
adj: list[list[int]] = [[] for _ in range(num_nodes)]
for u, v in edges:
adj[u].append(v)
# 0 = unvisited, 1 = in-progress, 2 = done
state = [0] * num_nodes
order: list[int] = []
has_cycle = False
def dfs(node: int) -> None:
nonlocal has_cycle
if has_cycle:
return
state[node] = 1
for neighbor in adj[node]:
if state[neighbor] == 1:
has_cycle = True
return
if state[neighbor] == 0:
dfs(neighbor)
state[node] = 2
order.append(node)
for i in range(num_nodes):
if state[i] == 0:
dfs(i)
if has_cycle:
return []
order.reverse()
return orderThis page lives in git. Anyone can propose an edit. Edit this page View source