In a directed graph with root , dominates if every path from to passes through . The strict dominators of form a chain; the closest one is the immediate dominator , and these edges form the dominator tree: dominates iff is an ancestor of in it. “Which vertices become unreachable if is deleted” = ‘s subtree. Lengauer–Tarjan computes it in (or ).

Lengauer–Tarjan

DFS from with preorder numbers. The semidominator is the smallest-numbered with a path whose internal vertices are all numbered higher than . Compute sdom in reverse preorder with a link-eval DSU (path compression carrying the minimum sdom), then convert to idom with the bucket rule.

struct Dominator {                                        // vertices 0..n-1
    int n, t = 0; vector<vector<int>> g, rg, bucket; vector<int> id, rev, par, sdom, dom, dsu, lab;
    Dominator(int n) : n(n), g(n), rg(n), bucket(n), id(n, -1), rev(n), par(n), sdom(n), dom(n), dsu(n), lab(n) {}
    void addEdge(int u, int v) { g[u].push_back(v); }
    void dfs(int u) {                                     // recursive; depth = DFS depth
        id[u] = t; rev[t] = u; lab[t] = sdom[t] = dsu[t] = t; t++;
        for (int w : g[u]) { if (id[w] < 0) { dfs(w); par[id[w]] = id[u]; } rg[id[w]].push_back(id[u]); }
    }
    int find(int u, int x = 0) {                          // link-eval with path compression
        if (u == dsu[u]) return x ? -1 : u;
        int v = find(dsu[u], x + 1);
        if (v < 0) return u;
        if (sdom[lab[dsu[u]]] < sdom[lab[u]]) lab[u] = lab[dsu[u]];
        dsu[u] = v;
        return x ? v : lab[u];
    }
    vector<int> build(int root) {                         // idom of every vertex, root -> root, unreachable -> -1
        dfs(root);
        for (int i = t - 1; i >= 0; i--) {
            for (int w : rg[i]) sdom[i] = min(sdom[i], sdom[find(w)]);
            if (i) bucket[sdom[i]].push_back(i);
            for (int w : bucket[i]) { int v = find(w); dom[w] = sdom[v] == sdom[w] ? sdom[w] : v; }
            if (i) dsu[i] = par[i];
        }
        for (int i = 1; i < t; i++) if (dom[i] != sdom[i]) dom[i] = dom[dom[i]];
        vector<int> res(n, -1); res[root] = root;
        for (int i = 1; i < t; i++) res[rev[i]] = rev[dom[i]];
        return res;
    }
};

Other methods

  • Iterative dataflow (Cooper–Harvey–Kennedy): in reverse postorder, repeat intersection of the idoms of its processed predecessors (walking two pointers up by postorder number) until nothing changes. Very short and fast on real-world graphs, worst case — and on a reducible flow graph (the structured, no-goto-into-a-loop-body case that essentially all compiler-generated and contest-constructed graphs are) it provably converges in at most passes, where is the loop nesting depth, which is why it’s fast in practice despite the worst case.
  • DAG: in topological order, = LCA (in the partially built tree) of all predecessors of ; with binary lifting .
  • Undirected connected graphs: there’s no useful dominator tree here in general — “every path from passes through ” is rarely true once the graph has cycles reachable both ways, so use the block-cut tree (Bridges and Biconnectivity) instead of forcing a directed concept onto an undirected graph.
  • Post-dominators: the same on the reversed graph from the exit vertex.
  • Edge dominators / bridges in the directed sense: subdivide each edge with a midpoint vertex; an edge dominates iff its midpoint does.
  • Alstrup et al. / Buchsbaum et al.: is achievable but the constants and implementation complexity make it purely theoretical interest for contests — Lengauer–Tarjan or CHK is always the right call in practice.

Uses

QuestionAnswer
vertices unreachable from after deleting subtree of (minus nothing else)
is on every – path? is an ancestor of
for each , number of vertices whose deletion cuts offdepth of in the dominator tree
”must pass” vertices between and path from to the root
compilers: SSA -placementdominance frontiers from the tree