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 ).
Proof: dominators of form a chain
Let both strictly dominate and suppose neither dominates the other: there are paths from to avoiding and from to avoiding . Take any path from to ; it contains both and . If the last occurrence of on comes after the last occurrence of , then followed by the part of after that last is an – walk avoiding , contradicting that dominates ; symmetrically in the other case. So any two strict dominators are comparable, they form a chain, and the deepest one is ; making it ‘s parent gives a tree whose ancestor relation is dominance.
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;
}
};Proof sketch: semidominators and the idom rule
Number vertices by DFS preorder. (1) : a semidominator path either is a single edge from a smaller vertex, or its last internal vertex chain lies in the DFS subtree of some that is an ancestor of the predecessor (DFS paths property: any path from a smaller to a larger vertex passes through a common ancestor). Processing in decreasing order, linked vertices are exactly those already processed, so
findover the DSU forest (a sub-forest of the DFS tree) returns the vertex with minimum sdom on the tree path, giving (1). (2) For , let be the vertex with minimum on the tree path from (exclusive) to (inclusive). If then ; otherwise . This is Lengauer–Tarjan’s main theorem: any path from to avoiding would have to enter the tree path below it via a vertex whose semidominator is smaller, which the minimum rules out in the first case. Buckets evaluate the path minimum exactly when all vertices of that path have been linked, and the final top-down pass resolves the deferred .
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
| Question | Answer |
|---|---|
| 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 off | depth of in the dominator tree |
| ”must pass” vertices between and | path from to the root |
| compilers: SSA -placement | dominance frontiers from the tree |