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

4.1.1 Adjacency Lists and DFS

4-Graphs/4.1.1_Adjacency_Lists_and_DFS.cpp

Stores a directed or undirected graph as adjacency lists. The list for node u contains its out-neighbors; an undirected edge is stored in both endpoints' lists. Nodes are nonnegative integer indices in $[0, {\htmlClass{math-inline-code}{\texttt{size()}}})$, and add_edge() grows the graph when either endpoint is new. The class also provides depth-first search, cycle detection, and forest/DAG checks.

Parallel edges and self-loops are stored as given, and the cycle checks read them as a multigraph would: a self-loop is a cycle, and a repeated undirected edge is a cycle of length two. The undirected search skips edges back to the parent by node index, so a duplicate is caught when the parent scans it rather than when the child looks back.

  • Graph(directed = true) constructs an empty graph, directed if directed is true and undirected otherwise.
  • size() returns the current number of nodes.
  • operator[u] returns a mutable or const reference to the adjacency list (std::vector<int>) of node u.
  • add_edge(u, v) adds an edge from u to v, plus the reverse edge if the graph is undirected, growing the node count to accommodate the larger index if necessary.
  • dfs(start, f) runs a depth-first search from node start, calling function f on each node in the order it is first visited.
  • has_cycle() returns whether the graph contains a cycle.
  • is_directed() returns whether the graph is directed.
  • is_forest() returns whether the graph is undirected and acyclic.
  • is_dag() returns whether the graph is directed and acyclic.

Implementation

#include <algorithm>
#include <vector>

class Graph {
  std::vector<std::vector<int>> adj;
  bool directed;

 public:
  explicit Graph(bool directed = true) : directed(directed) {}

  int size() const { return static_cast<int>(adj.size()); }
  std::vector<int> &operator[](int u) { return adj[u]; }
  const std::vector<int> &operator[](int u) const { return adj[u]; }

  void add_edge(int u, int v) {
    int n = static_cast<int>(adj.size());
    if (u >= n || v >= n) {
      adj.resize(std::max(u, v) + 1);
    }
    adj[u].push_back(v);
    if (!directed) {
      adj[v].push_back(u);
    }
  }

  template<typename Fn>
  void dfs(int start, Fn f) const {
    std::vector<char> visit(adj.size());
    auto dfs = [&](auto &&dfs, int u) -> void {
      f(u);
      visit[u] = true;
      for (int v : adj[u]) {
        if (!visit[v]) {
          dfs(dfs, v);
        }
      }
    };
    dfs(dfs, start);
  }

  bool has_cycle() const {
    int n = static_cast<int>(adj.size());
    std::vector<char> visit(n), onstack(n);
    auto dfs = [&](auto &&dfs, int u, int p) -> bool {
      visit[u] = true;
      onstack[u] = true;
      for (int v : adj[u]) {
        if ((directed && onstack[v]) ||           // back edge in directed graph
            (!directed && visit[v] && v != p) ||  // back edge in undirected graph
            (!visit[v] && dfs(dfs, v, u))) {      // tree edge to unvisited node
          return true;
        }
      }
      onstack[u] = false;
      return false;
    };
    for (int i = 0; i < n; i++) {
      if (!visit[i] && dfs(dfs, i, -1)) {
        return true;
      }
    }
    return false;
  }

  bool is_directed() const { return directed; }
  bool is_forest() const { return !directed && !has_cycle(); }
  bool is_dag() const { return directed && !has_cycle(); }
};

Example Usage

#include <cassert>
using namespace std;

int main() {
  //           0
  //       /  /  |
  //      v  v   v
  //      1  6   7
  //    / |     / |
  //   v  v    v  v
  //   2  5    8  11
  //  / |     /|
  // v  v    v v
  // 3  4    9 10
  Graph g;
  g.add_edge(0, 1);
  g.add_edge(0, 6);
  g.add_edge(0, 7);
  g.add_edge(1, 2);
  g.add_edge(1, 5);
  g.add_edge(2, 3);
  g.add_edge(2, 4);
  g.add_edge(7, 8);
  g.add_edge(7, 11);
  g.add_edge(8, 9);
  g.add_edge(8, 10);
  vector<int> order;
  g.dfs(0, [&](int u) { order.push_back(u); });
  assert((order == vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}));
  assert(g.size() == 12 && g[0].size() == 3);
  assert(g.is_dag());
  assert(!g.has_cycle());

  //       0
  //     /  |
  //    1   2
  //  / |
  // 3  4
  Graph tree(false);
  tree.add_edge(0, 1);
  tree.add_edge(0, 2);
  tree.add_edge(1, 3);
  tree.add_edge(1, 4);
  assert(tree.is_forest());
  assert(!tree.is_dag());
  tree.add_edge(2, 3);
  assert(!tree.is_forest());
  return 0;
}