Chapter 13: Topological Sorting (Advanced Graphs)

Chapter 13: Topological Sorting (Advanced Graphs)

Why Topological Sorting?

Topological Sorting is a fundamental algorithm in graph theory used for problems involving dependency resolution, build order, and task scheduling. It applies to Directed Acyclic Graphs (DAGs) and provides a linear ordering of nodes such that for every directed edge u -> v, node u appears before v in the ordering.

Key Use Cases

  • Dependency Resolution: Ensuring correct execution order in systems like package managers.
  • Build Order Problems: Determining the correct sequence of tasks with dependencies.
  • Scheduling Tasks: Solving real-world scheduling and precedence problems efficiently.

Example Problems

  1. Course Schedule (Medium)
  2. Alien Dictionary (Hard)

Topological Sorting Algorithms

1. Kahn’s Algorithm (BFS Approach)

This approach uses indegree tracking to iteratively process nodes with zero incoming edges.

Algorithm Steps

  1. Compute the indegree (number of incoming edges) for each node.
  2. Add all nodes with indegree 0 to a queue.
  3. While the queue is not empty:
    • Remove a node from the queue and append it to the topological order.
    • Reduce the indegree of its neighbors.
    • If any neighbor's indegree becomes 0, add it to the queue.
  4. If all nodes are processed, return the order; otherwise, a cycle exists.

Implementation

from collections import deque

def topological_sort_kahn(graph, num_nodes):
    indegree = {i: 0 for i in range(num_nodes)}
    for node in graph:
        for neighbor in graph[node]:
            indegree[neighbor] += 1

    queue = deque([node for node in indegree if indegree[node] == 0])
    topo_order = []

    while queue:
        node = queue.popleft()
        topo_order.append(node)
        for neighbor in graph[node]:
            indegree[neighbor] -= 1
            if indegree[neighbor] == 0:
                queue.append(neighbor)

    return topo_order if len(topo_order) == num_nodes else []  # Detect cycle

2. DFS-Based Topological Sorting

A Depth-First Search (DFS) approach recursively explores nodes and records the finishing order to determine the topological order.

Algorithm Steps

  1. Maintain a visited set to track processed nodes.
  2. Perform DFS and push nodes to a stack after all their neighbors are visited.
  3. The final topological order is obtained by reversing the stack.

Implementation

def topological_sort_dfs(graph, num_nodes):
    visited = set()
    stack = []

    def dfs(node):
        if node in visited:
            return
        visited.add(node)
        for neighbor in graph[node]:
            dfs(neighbor)
        stack.append(node)

    for node in range(num_nodes):
        if node not in visited:
            dfs(node)

    return stack[::-1]  # Reverse to get correct order

Trade-offs & Complexity Analysis

ApproachTime ComplexitySpace ComplexityNotes
Kahn’s Algorithm (BFS)O(V + E)O(V + E)Good for iterative processing
DFS-based SortingO(V + E)O(V + E)Useful for problems involving recursion

Key Takeaways

  1. Use Kahn’s Algorithm (BFS) when iterative processing is required (e.g., resolving dependencies in layers).
  2. Use DFS-Based Sorting when recursion is natural and we need to track finishing order.
  3. Topological sorting only works on DAGs—cycle detection is crucial before applying it.

Practice Problems

  • LeetCode 207: Course Schedule
  • LeetCode 210: Course Schedule II
  • LeetCode 269: Alien Dictionary

Conclusion

Topological Sorting is an essential technique for solving dependency-based problems efficiently. By mastering Kahn’s Algorithm (BFS) and DFS-based Sorting, you can tackle complex scheduling, ordering, and graph traversal problems effectively.