DSA DSA In Depth
0%
lesson topological-sortdagkahn-algorithm

Directed Acyclic Graphs (DAGs): Kahn's Indegree Queue vs DFS Post-Order Reversal

7 min read Module 5.1: Graph Representations, Traversals, Connectivity & Topological Order Updated Oct 23, 2026
📄 Cheat Sheet
Topological Sort Algorithm | Graph Theory
William Fiset Topological Sort Algorithm | Graph Theory Watch on YouTube ↗

Explains topological linear extensions on DAGs and walks through post-order DFS and Kahn's algorithm.

0:00 / --:--

Mathematical Duality: Source Peeling vs. Finish-Time Ordering

A directed graph $G = (V, E)$ admits a topological ordering if and only if $G$ is a DAG (contains no directed cycles). Both standard algorithms exploit complementary structural invariants of DAGs.

Formal Invariant — Finish-Time Monotonicity on DAGs

Theorem (CLRS Theorem 20.11): A directed graph $G$ is acyclic if and only if a DFS of $G$ yields no back edges. Furthermore, for every directed edge $(u, v) \in E$ in a DAG:

$$\text{tout}[u] > \text{tout}[v]$$

Proof: When edge $(u, v)$ is explored from $u$, $v$ cannot be GRAY (otherwise $(u, v)$ would be a back edge, contradicting acyclicity). If $v$ is WHITE, DFS recurses on $v$ and finishes $v$ before finishing $u$, so $\text{tout}[v] < \text{tout}[u]$. If $v$ is already BLACK, $v$ finished before $u$ even finished, so $\text{tout}[v] < \text{tout}[u]$. Ordering vertices in decreasing order of $\text{tout}$ therefore places $u$ before $v$ for every edge $(u, v)$.

1. Kahn's Algorithm (1962): Indegree Peeling & Cycle Detection

Every non-empty finite DAG has at least one vertex with $\text{indegree}(u) = 0$ (otherwise walking backward along incoming edges would repeat a vertex in $\le |V|$ steps, forming a cycle). Kahn's algorithm maintains an array $\text{in}[0 \dots V-1]$ and a ready queue $Q$ of all vertices with $\text{in}[u] == 0$:

  1. Pop $u$ from $Q$ and append $u$ to the topological order.
  2. For each outgoing edge $(u, v)$, decrement $\text{in}[v]$. If $\text{in}[v]$ reaches $0$, push $v$ into $Q$.
  3. Built-in Cycle Verification: If the loop terminates and the output list has fewer than $|V|$ vertices, the remaining vertices with $\text{in}[w] > 0$ contain at least one directed cycle (and everything reachable only from those cycles).

2. Lexicographically Smallest Topological Sort & Parallel Layering

Kahn's algorithm offers two capabilities that DFS post-order reversal cannot match directly:

  • Lexicographically Smallest Topological Order: Replace the FIFO queue $Q$ in Kahn's algorithm with a min-heap (std::priority_queue<int, vector<int>, greater<int>>). At every step, among all currently unconstrained tasks ($ ext{in}[u] = 0$), picking the minimum index $u$ greedily produces the lexicographically smallest valid topological sequence in $O((V + E) \log V)$. (Note: sorting adjacency lists in DFS does not produce the lexicographically smallest topological sort!).
  • Parallel Execution Waves (Level-by-Level Kahn): Processing all current zero-indegree vertices in batch waves yields the minimum number of parallel stages needed to execute a task graph with infinite processors—equal to the longest path in the DAG.
Kahn's Indegree Peeling vs. Parallel Execution Waves Wave 0 (in=0) 0 1 Wave 1 2 3 Wave 2 (Sink) 4
flowchart LR Init["Compute indegree[0..V-1]"] --> Seed["Push all u with indegree[u] == 0 to Queue"] Seed --> Loop{"Queue empty?"} Loop -- "No" --> Pop["Pop u, append to order, decrement indegree[v] for (u,v)"] Pop --> PushZero["If indegree[v] == 0, push v to Queue"] PushZero --> Loop Loop -- "Yes" --> Check{"order.size() == V?"} Check -- "Yes" --> Valid["Valid Topological Order"] Check -- "No" --> Cycle["Cycle Detected!"]
Kahn’s Indegree Queue Topological Sort, Bazel Cycle Detection & 2-SAT Solver
O(V + E) TopoSort · 2-SAT Implication SCC

Peel indegree-0 vertices with Kahn’s queue while relaxing longest-path DAG DP, detect unpeeled cyclic cores in Bazel/Cargo build graphs, or solve 2-SAT via implication graph (¬a → b) SCC topological order.

Step 1 / 9
Custom Input:
Timeline

Invariant & State

Initialize Kahn queue with all indegree-0 vertices: Q = [core]. Critical path dp[core] = 0ms.
+4ms +7ms +3ms +5ms +3ms core in=0,dp=0 auth in=1,dp=0 db in=1,dp=0 api in=2,dp=0 app in=1,dp=0
Indegree-0 Queue
[core]
Topological Order
[]
Critical Path DP
15 ms (core→db→api→app)
[Step 1] Initialize Kahn queue with all indegree-0 vertices: Q = [core]. Critical path dp[core] = 0ms.
Hardware Reality — Iterative In-Place Queue vs. Recursion Overhead

In Kahn's algorithm, the output vector order itself can act as the FIFO queue: push initial zero-indegree nodes into order, maintain a read index readIdx = 0, and loop while readIdx < order.size(), appending newly unlocked zero-indegree neighbors directly to the back of order. This eliminates a separate std::queue allocation and performs strictly sequential writes and reads in a single contiguous vector.

Complexity Matrix

Algorithm Time Complexity Auxiliary Space Cycle Detection Lexicographical Order
Kahn's Algorithm (FIFO Queue) O(V + E) O(V) order.size() < V No (wave order)
Kahn's Algorithm (Min-Heap) O((V + E) log V) O(V) order.size() < V Yes (exact global min)
DFS Post-Order Reversal O(V + E) O(V) Requires 3-color GRAY check No

Production Implementation: Zero-Allocation In-Place Kahn's Algorithm

#include <vector>
#include <queue>

// Returns topological order of size V, or an empty vector if G contains a directed cycle.
std::vector<int> topologicalSortKahn(int n, const std::vector<std::vector<int>>& adj) {
    std::vector<int> indegree(n, 0);
    for (int u = 0; u < n; ++u) {
        for (int v : adj[u]) {
            ++indegree[v];
        }
    }

    // Use the output vector itself as the FIFO queue (zero secondary queue allocation)
    std::vector<int> order;
    order.reserve(n);
    for (int u = 0; u < n; ++u) {
        if (indegree[u] == 0) {
            order.push_back(u);
        }
    }

    for (size_t readIdx = 0; readIdx < order.size(); ++readIdx) {
        int u = order[readIdx];
        for (int v : adj[u]) {
            if (--indegree[v] == 0) {
                order.push_back(v);
            }
        }
    }

    if (static_cast<int>(order.size()) != n) {
        return {}; // Directed cycle exists
    }
    return order;
}
Failure Mode — Running DFS Post-Order Without 3-Color Cycle Verification

A frequent bug is running a simple 2-state (visited[u] boolean) DFS that appends $u$ on post-order and reverses the result, assuming the input graph is a DAG. On a graph with a cycle, a 2-state DFS still returns a permutation of length $|V|$ without throwing an error! If using DFS for topological sort on untrusted graphs, you must maintain 3 colors (WHITE/GRAY/BLACK) and abort if any edge targets a GRAY vertex.

Check yourself Question 1 of 4
Concept Check Finish-Time Inequality on DAG Edges recall
Section: DFS Post-Order Invariant

In any Depth-First Search of a Directed Acyclic Graph (DAG), what relationship holds between the finish timestamps `tout[u]` and `tout[v]` for every directed edge $(u, v) \in E$?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

How can you determine in $O(V + E)$ time whether a DAG has a *unique* topological ordering (which is equivalent to containing a directed Hamiltonian path)?

Key Takeaways

A directed graph has a valid topological ordering if and only if it is a Directed Acyclic Graph (DAG).
In any DFS of a DAG, every directed edge (u, v) satisfies tout[u] > tout[v], so reversing the DFS post-order yields a valid topological sort.
Kahn's algorithm iteratively extracts zero-indegree vertices in O(V + E) time and detects cycles automatically whenever fewer than V vertices are emitted.
Replacing Kahn's FIFO queue with a min-heap produces the lexicographically smallest topological ordering in O((V + E) log V) time.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue