A directed graph has a topological order iff it is acyclic. Kahn’s algorithm (repeatedly remove an in-degree-0 vertex) and DFS finishing order both find one in and both detect cycles. In topological order every DAG DP is one loop: shortest/longest paths with any weights, path counts, reachability (DAG DP). For graphs with cycles, condense SCCs first (SCC).
vector<int> kahn(const vector<vector<int>>& g) { // empty if there is a cycle
int n = sz(g); vector<int> in(n), ord;
for (auto& e : g) for (int v : e) in[v]++;
for (int v = 0; v < n; v++) if (!in[v]) ord.push_back(v);
for (int i = 0; i < sz(ord); i++) for (int v : g[ord[i]]) if (!--in[v]) ord.push_back(v);
return sz(ord) == n ? ord : vector<int>{};
}DFS version: append each vertex when it finishes, then reverse. Prefer Kahn when recursion depth is a concern (it’s iterative by construction) or when you need the lexicographically-smallest order via a heap; prefer the DFS version when the topological order is only a preprocessing step inside a larger DFS-based algorithm (e.g. 2-SAT, which assigns truth values in reverse topological order of the implication graph’s condensation).
Proof: Kahn and the DFS order are topological orders, and cycles are detected
Kahn: a vertex is appended only after all its in-edges were removed, i.e. after all its predecessors were appended. If the graph is a DAG, every nonempty remaining subgraph has a source (otherwise walking backwards forever would repeat a vertex: a cycle), so the process never gets stuck. If there is a cycle, no vertex on it can reach in-degree 0 before another vertex on it is removed, so none is ever removed and fewer than vertices are output.
DFS: for an edge in a DAG, when scans it, is either white (then finishes inside ‘s call, before ) or black (already finished); it can’t be grey, since a grey is an ancestor of and the edge would close a cycle. So finishes before , and reversing finishing order puts first.
Variants
| Need | How |
|---|---|
| lexicographically smallest order | Kahn with a min-heap |
| vertex 1 as early as possible, then 2, then 3, … (not lexicographic!) | Kahn on the reversed graph with a max-heap, then reverse the output |
| is the order unique? | Kahn’s queue never holds two vertices at once ⟺ there is a Hamiltonian path |
| count topological orders | bitmask DP ; for a forest (tree poset) |
| layers / longest chain | (Mirsky: min antichain cover = longest chain) |
| min number of chains covering (paths may share vertices) | Dilworth: transitive closure + bipartite matching |
| parallel scheduling with machines and unit jobs | NP-hard for general ; is polynomial (Coffman–Graham); trees: Hu’s level algorithm |
| longest simple path | linear time via the DAG DP above — note this is specifically what acyclicity buys you, since longest simple path is NP-hard on general graphs |
Proof: the reversed max-heap trick and the tree formula
Reversed max-heap: compare orders by lexicographically. Let be the largest sink. Take any valid order not ending with , and move to the end; this stays valid because has no successors. Vertices that were after move one step earlier, moves later, nothing else changes. The last vertex of is a sink , so and was after : the smallest label whose position changed is and it moved earlier, so the new order is strictly better. Hence every optimal order ends with ; remove and repeat on the rest. That is exactly Kahn on the reversed graph with a max-heap, output reversed.
Trees: for a rooted forest where each vertex must come after its parent, a uniformly random permutation is valid iff every vertex is the first of its subtree, events that are independent across vertices with probabilities . So the count is .
Mirsky: label by the number of vertices on the longest chain ending at . Two comparable vertices get different labels, so each label class is an antichain, and the number of classes is the longest chain; fewer antichains can’t work since a chain meets each antichain at most once.
Reachability
Transitive closure with bitsets: in reverse topological order, . time and bytes ( is about 312 MB; process 64 targets at a time with one word per vertex when memory is tight).
vector<vector<ull>> closure(const vector<vector<int>>& g, const vector<int>& topo) { // g a DAG
int n = sz(g), W = (n + 63) / 64; vector<vector<ull>> r(n, vector<ull>(W));
for (int i = n - 1; i >= 0; i--) {
int u = topo[i]; r[u][u >> 6] |= 1ULL << (u & 63);
for (int v : g[u]) for (int w = 0; w < W; w++) r[u][w] |= r[v][w];
}
return r;
}Incremental reachability (insert edges into a DAG, reject edges creating a cycle, queries) in amortised per insertion, memory: for every source keep a spanning tree of the vertices reachable from ; inserting grafts the part of ‘s tree that doesn’t reach yet into the tree of every that reaches (Italiano).
struct IncReach {
int n; vector<vector<int>> par; vector<vector<vector<int>>> ch; // par[p][v]: parent of v in p's tree, -1 unreachable
IncReach(int n) : n(n), par(n, vector<int>(n, -1)), ch(n, vector<vector<int>>(n)) {}
bool reach(int s, int t) const { return s == t || par[s][t] >= 0; }
bool addEdge(int s, int t) {
if (reach(t, s)) return false; // would close a cycle
if (reach(s, t)) return true;
for (int p = 0; p < n; p++) if (reach(p, s) && !reach(p, t)) meld(p, t, s, t);
return true;
}
void meld(int root, int sub, int u, int v) {
par[root][v] = u; ch[root][u].push_back(v);
for (int c : ch[sub][v]) if (!reach(root, c)) meld(root, sub, v, c);
}
};Proof: Italiano's grafting is in total
Correctness: after inserting , the vertices newly reachable from are exactly those reachable from but not yet from ; walking ‘s own reachability tree and stopping at vertices already reaches visits exactly them (if already reaches , it already reaches everything reaches). Cost: every step of
meld(root, ...)makes a new pair reachable, and each of the pairs becomes reachable once; scanning children of insub’s tree is charged to the newly reachable , and a vertex has at most children per tree, so the grafting work is over any sequence of insertions, plus per insertion for the loop over .
For general static reachability queries on large sparse graphs there is no near-linear exact method; options are bitsets (), offline processing in blocks of 64 targets, or exploiting structure (planar/trees: interval labels; DAGs from DP: problem-specific).