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).

Variants

NeedHow
lexicographically smallest orderKahn 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 ordersbitmask 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 jobsNP-hard for general ; is polynomial (Coffman–Graham); trees: Hu’s level algorithm
longest simple pathlinear time via the DAG DP above — note this is specifically what acyclicity buys you, since longest simple path is NP-hard on general graphs

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);
    }
};

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).