Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / DFS and Trees

4.1.2 Topological Sort

4-Graphs/4.1.2_Topological_Sort.cpp

Given a directed acyclic graph (DAG), find one of possibly many orderings of the nodes such that for every edge from node $u$ to $v$, $u$ comes before $v$ in the ordering.

The DFS version uses the fact that every outgoing dependency of a node is finished before the node itself is appended, so reversing DFS post-order places each prerequisite before everything it can reach. Kahn's algorithm instead repeatedly removes nodes with indegree zero: these are exactly the nodes whose remaining prerequisites have all already been placed. If at some point not every node can be removed, the leftover nodes must lie on a directed cycle.

  • toposort_dfs() returns a valid topological ordering using DFS post-order, or an empty vector if the graph contains a cycle.
  • toposort_kahn() returns a valid topological ordering using indegrees and a queue, or returns an empty vector if the graph contains a cycle.

Implementation

#include <algorithm>
#include <cassert>
#include <queue>
#include <vector>

std::vector<std::vector<int>> adj;

std::vector<int> toposort_dfs() {
  int n = static_cast<int>(adj.size());
  std::vector<char> state(n);
  std::vector<int> res;
  auto dfs = [&](auto &&dfs, int u) -> bool {
    if (state[u] == 1) {
      return false;
    }
    if (state[u] == 2) {
      return true;
    }
    state[u] = 1;
    for (int v : adj[u]) {
      if (!dfs(dfs, v)) {
        return false;
      }
    }
    state[u] = 2;
    res.push_back(u);
    return true;
  };
  for (int i = 0; i < n; i++) {
    if (state[i] == 0 && !dfs(dfs, i)) {
      return {};
    }
  }
  std::reverse(res.begin(), res.end());
  return res;
}

std::vector<int> toposort_kahn() {
  int n = static_cast<int>(adj.size());
  std::vector<int> indegree(n), res;
  for (int u = 0; u < n; u++) {
    for (int v : adj[u]) {
      indegree[v]++;
    }
  }
  std::queue<int> q;
  for (int i = 0; i < n; i++) {
    if (indegree[i] == 0) {
      q.push(i);
    }
  }
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    res.push_back(u);
    for (int v : adj[u]) {
      if (--indegree[v] == 0) {
        q.push(v);
      }
    }
  }
  if (static_cast<int>(res.size()) != n) {
    return {};
  }
  return res;
}

Example Usage

using namespace std;

int main() {
  //         1
  //         |
  //         v
  // 0 ----> 3 ---> 5
  // |     / |
  // v    v  v
  // 4 -> 6  7 <--- 2
  // ^______________|
  adj.assign(8, {});
  adj[0].push_back(3);
  adj[0].push_back(4);
  adj[1].push_back(3);
  adj[2].push_back(4);
  adj[2].push_back(7);
  adj[3].push_back(5);
  adj[3].push_back(6);
  adj[3].push_back(7);
  adj[4].push_back(6);
  vector<int> dfs_res = toposort_dfs();
  assert((dfs_res == vector<int>{2, 1, 0, 4, 3, 7, 6, 5}));
  vector<int> kahn = toposort_kahn();
  assert(dfs_res.size() == adj.size());
  assert(kahn.size() == adj.size());
  vector<int> position(kahn.size());
  for (int i = 0; i < static_cast<int>(kahn.size()); i++) {
    position[kahn[i]] = i;
  }
  for (int u = 0; u < static_cast<int>(adj.size()); u++) {
    for (int v : adj[u]) {
      assert(position[u] < position[v]);
    }
  }
  adj = {{1}, {0}};
  assert(toposort_dfs().empty());
  assert(toposort_kahn().empty());
  return 0;
}