Dominator Tree
(graph/dominator_tree.hpp)
- View this file on GitHub
- Last update: 2026-08-13 01:41:40+09:00
- Include:
#include "graph/dominator_tree.hpp"
Overview
In a directed graph rooted at root, vertex u dominates vertex v when
every directed path from root to v passes through u.
dominator_tree(graph, root) computes immediate dominators with the
Lengauer-Tarjan algorithm. The immediate dominator of v is the closest strict
dominator of v; these edges form the dominator tree.
Only vertices reachable from root belong to the tree. Inactive edges are
ignored.
Result
DominatorTree exposes:
| Member | Description |
|---|---|
root |
Start vertex used for the computation. |
immediate_dominator[v] |
Immediate dominator of v; the root dominates itself, and unreachable vertices store -1. |
children[v] |
Children of v in the dominator tree. |
dfs_order |
Reachable vertices in the original graph’s DFS discovery order. |
tin, tout
|
Euler intervals of the dominator tree; unreachable vertices store -1. |
Methods:
| Method | Description | Complexity |
|---|---|---|
size() |
Returns the original graph’s vertex count. | $O(1)$ |
reachable(v) |
Returns whether v is reachable from the root. |
$O(1)$ |
dominates(u, v) |
Returns whether u dominates v. |
$O(1)$ |
Complexity
The Lengauer-Tarjan algorithm runs in near-linear time, $O((N+M)\alpha(N,M))$, and uses $O(N+M)$ memory.
The graph traversal and dominator-tree Euler traversal are iterative, avoiding recursion-depth issues on long paths.
Example
#include "graph/dominator_tree.hpp"
#include "graph/graph.hpp"
#include <iostream>
int main() {
m1une::graph::Graph<> graph(4);
graph.add_directed_edge(0, 1);
graph.add_directed_edge(0, 2);
graph.add_directed_edge(1, 3);
graph.add_directed_edge(2, 3);
auto tree = m1une::graph::dominator_tree(graph, 0);
std::cout << tree.immediate_dominator[3] << "\n"; // 0
std::cout << tree.dominates(0, 3) << "\n"; // 1
}
Depends on
Required by
Verified with
verify/graph/cow_game.test.cpp
verify/graph/dominator_tree.test.cpp
verify/graph/graph_algorithms.test.cpp
verify/graph/range_edge_graph.test.cpp
Code
#ifndef M1UNE_GRAPH_DOMINATOR_TREE_HPP
#define M1UNE_GRAPH_DOMINATOR_TREE_HPP 1
#include <cassert>
#include <utility>
#include <vector>
#include "graph.hpp"
namespace m1une {
namespace graph {
struct DominatorTree {
int root;
std::vector<int> immediate_dominator;
std::vector<std::vector<int>> children;
std::vector<int> dfs_order;
std::vector<int> tin;
std::vector<int> tout;
int size() const {
return int(immediate_dominator.size());
}
bool reachable(int vertex) const {
assert(0 <= vertex && vertex < size());
return immediate_dominator[vertex] != -1;
}
bool dominates(int ancestor, int vertex) const {
assert(0 <= ancestor && ancestor < size());
assert(0 <= vertex && vertex < size());
return
reachable(ancestor) &&
reachable(vertex) &&
tin[ancestor] <= tin[vertex] &&
tin[vertex] < tout[ancestor];
}
};
// Lengauer-Tarjan immediate dominators from one start vertex.
template <class T>
DominatorTree dominator_tree(const Graph<T>& graph, int root) {
int n = graph.size();
assert(0 <= root && root < n);
std::vector<int> dfs_index(n, -1);
std::vector<int> vertex;
std::vector<int> parent_vertex(n, -1);
std::vector<std::pair<int, int>> stack;
dfs_index[root] = 0;
vertex.push_back(root);
stack.emplace_back(root, 0);
while (!stack.empty()) {
int current = stack.back().first;
int& edge_index = stack.back().second;
if (edge_index == int(graph[current].size())) {
stack.pop_back();
continue;
}
const auto& edge = graph[current][edge_index++];
if (!edge.alive || dfs_index[edge.to] != -1) continue;
parent_vertex[edge.to] = current;
dfs_index[edge.to] = int(vertex.size());
vertex.push_back(edge.to);
stack.emplace_back(edge.to, 0);
}
int reachable_count = int(vertex.size());
std::vector<std::vector<int>> predecessor(reachable_count);
for (int from : vertex) {
for (const auto& edge : graph[from]) {
if (!edge.alive || dfs_index[edge.to] == -1) continue;
predecessor[dfs_index[edge.to]].push_back(dfs_index[from]);
}
}
std::vector<int> parent(reachable_count, -1);
for (int index = 1; index < reachable_count; ++index) {
parent[index] = dfs_index[parent_vertex[vertex[index]]];
}
std::vector<int> semi(reachable_count);
std::vector<int> idom(reachable_count, -1);
std::vector<int> ancestor(reachable_count, -1);
std::vector<int> label(reachable_count);
std::vector<std::vector<int>> bucket(reachable_count);
for (int index = 0; index < reachable_count; ++index) {
semi[index] = index;
label[index] = index;
}
auto compress = [&](int start) {
std::vector<int> path;
int current = start;
while (
ancestor[current] != -1 &&
ancestor[ancestor[current]] != -1
) {
path.push_back(current);
current = ancestor[current];
}
for (int index = int(path.size()) - 1; index >= 0; --index) {
int node = path[index];
int parent_node = ancestor[node];
if (semi[label[parent_node]] < semi[label[node]]) {
label[node] = label[parent_node];
}
ancestor[node] = ancestor[parent_node];
}
};
auto eval = [&](int node) {
if (ancestor[node] == -1) return label[node];
compress(node);
int parent_node = ancestor[node];
if (semi[label[parent_node]] < semi[label[node]]) {
return label[parent_node];
}
return label[node];
};
for (int current = reachable_count - 1; current >= 1; --current) {
for (int previous : predecessor[current]) {
semi[current] = std::min(semi[current], semi[eval(previous)]);
}
bucket[semi[current]].push_back(current);
ancestor[current] = parent[current];
int parent_node = parent[current];
for (int node : bucket[parent_node]) {
int best = eval(node);
idom[node] =
semi[best] < semi[node] ? best : parent_node;
}
bucket[parent_node].clear();
}
for (int current = 1; current < reachable_count; ++current) {
if (idom[current] != semi[current]) {
idom[current] = idom[idom[current]];
}
}
idom[0] = 0;
DominatorTree result;
result.root = root;
result.immediate_dominator.assign(n, -1);
result.children.assign(n, {});
result.dfs_order = vertex;
for (int index = 0; index < reachable_count; ++index) {
int current = vertex[index];
int dominator = vertex[idom[index]];
result.immediate_dominator[current] = dominator;
if (current != root) result.children[dominator].push_back(current);
}
result.tin.assign(n, -1);
result.tout.assign(n, -1);
int timer = 0;
std::vector<std::pair<int, int>> tree_stack;
tree_stack.emplace_back(root, 0);
result.tin[root] = timer++;
while (!tree_stack.empty()) {
int current = tree_stack.back().first;
int& child_index = tree_stack.back().second;
if (child_index == int(result.children[current].size())) {
result.tout[current] = timer;
tree_stack.pop_back();
continue;
}
int child = result.children[current][child_index++];
result.tin[child] = timer++;
tree_stack.emplace_back(child, 0);
}
return result;
}
} // namespace graph
} // namespace m1une
#endif // M1UNE_GRAPH_DOMINATOR_TREE_HPP#line 1 "graph/dominator_tree.hpp"
#include <cassert>
#include <utility>
#include <vector>
#line 1 "graph/graph.hpp"
#include <array>
#line 8 "graph/graph.hpp"
namespace m1une {
namespace graph {
template <class T = int>
struct Edge {
using cost_type = T;
int from;
int to;
T cost;
int id;
bool alive;
Edge() : from(-1), to(-1), cost(T()), id(-1), alive(true) {}
Edge(int from_, int to_, T cost_ = T(1), int id_ = -1, bool alive_ = true)
: from(from_), to(to_), cost(cost_), id(id_), alive(alive_) {}
int other(int v) const {
assert(v == from || v == to);
return from ^ to ^ v;
}
};
template <class T = int>
struct Graph {
using edge_type = Edge<T>;
using cost_type = T;
private:
struct EdgePositions {
std::array<std::pair<int, int>, 2> value{};
int size = 0;
void push_back(std::pair<int, int> position) {
assert(size < 2);
value[size++] = position;
}
};
int _n;
int _edge_count;
std::vector<std::vector<edge_type>> _g;
std::vector<EdgePositions> _edge_positions;
public:
Graph() : _n(0), _edge_count(0) {}
explicit Graph(int n) : _n(n), _edge_count(0), _g(n) {
assert(0 <= n);
}
int size() const {
return _n;
}
bool empty() const {
return _n == 0;
}
int edge_count() const {
return _edge_count;
}
int add_vertex() {
_g.emplace_back();
return _n++;
}
int add_directed_edge(int from, int to, T cost = T(1)) {
assert(0 <= from && from < _n);
assert(0 <= to && to < _n);
int id = _edge_count++;
int idx = int(_g[from].size());
_g[from].push_back(edge_type(from, to, cost, id));
_edge_positions.emplace_back();
_edge_positions.back().push_back({from, idx});
return id;
}
int add_edge(int u, int v, T cost = T(1)) {
assert(0 <= u && u < _n);
assert(0 <= v && v < _n);
int id = _edge_count++;
int u_idx = int(_g[u].size());
_g[u].push_back(edge_type(u, v, cost, id));
int v_idx = int(_g[v].size());
_g[v].push_back(edge_type(v, u, cost, id));
_edge_positions.emplace_back();
_edge_positions.back().push_back({u, u_idx});
_edge_positions.back().push_back({v, v_idx});
return id;
}
void set_edge_alive(int id, bool alive) {
assert(0 <= id && id < _edge_count);
for (int i = 0; i < _edge_positions[id].size; ++i) {
auto [v, idx] = _edge_positions[id].value[i];
_g[v][idx].alive = alive;
}
}
void erase_edge(int id) {
set_edge_alive(id, false);
}
void revive_edge(int id) {
set_edge_alive(id, true);
}
bool is_edge_alive(int id) const {
assert(0 <= id && id < _edge_count);
assert(_edge_positions[id].size != 0);
auto [v, idx] = _edge_positions[id].value[0];
return _g[v][idx].alive;
}
const std::vector<edge_type>& operator[](int v) const {
assert(0 <= v && v < _n);
return _g[v];
}
std::vector<edge_type>& operator[](int v) {
assert(0 <= v && v < _n);
return _g[v];
}
const std::vector<std::vector<edge_type>>& adjacency() const {
return _g;
}
std::vector<std::vector<edge_type>>& adjacency() {
return _g;
}
std::vector<edge_type> edges(bool include_inactive = false) const {
std::vector<edge_type> result;
result.reserve(_edge_count);
std::vector<char> used(_edge_count, false);
for (int v = 0; v < _n; v++) {
for (const auto& e : _g[v]) {
if (!include_inactive && !e.alive) continue;
if (0 <= e.id && e.id < _edge_count) {
if (used[e.id]) continue;
used[e.id] = true;
}
result.push_back(e);
}
}
return result;
}
Graph reversed() const {
Graph result(_n);
result._edge_count = _edge_count;
result._edge_positions.assign(_edge_count, {});
for (int v = 0; v < _n; v++) {
for (const auto& e : _g[v]) {
int idx = int(result._g[e.to].size());
result._g[e.to].push_back(edge_type(e.to, e.from, e.cost, e.id, e.alive));
if (0 <= e.id && e.id < _edge_count) result._edge_positions[e.id].push_back({e.to, idx});
}
}
return result;
}
};
} // namespace graph
} // namespace m1une
#line 9 "graph/dominator_tree.hpp"
namespace m1une {
namespace graph {
struct DominatorTree {
int root;
std::vector<int> immediate_dominator;
std::vector<std::vector<int>> children;
std::vector<int> dfs_order;
std::vector<int> tin;
std::vector<int> tout;
int size() const {
return int(immediate_dominator.size());
}
bool reachable(int vertex) const {
assert(0 <= vertex && vertex < size());
return immediate_dominator[vertex] != -1;
}
bool dominates(int ancestor, int vertex) const {
assert(0 <= ancestor && ancestor < size());
assert(0 <= vertex && vertex < size());
return
reachable(ancestor) &&
reachable(vertex) &&
tin[ancestor] <= tin[vertex] &&
tin[vertex] < tout[ancestor];
}
};
// Lengauer-Tarjan immediate dominators from one start vertex.
template <class T>
DominatorTree dominator_tree(const Graph<T>& graph, int root) {
int n = graph.size();
assert(0 <= root && root < n);
std::vector<int> dfs_index(n, -1);
std::vector<int> vertex;
std::vector<int> parent_vertex(n, -1);
std::vector<std::pair<int, int>> stack;
dfs_index[root] = 0;
vertex.push_back(root);
stack.emplace_back(root, 0);
while (!stack.empty()) {
int current = stack.back().first;
int& edge_index = stack.back().second;
if (edge_index == int(graph[current].size())) {
stack.pop_back();
continue;
}
const auto& edge = graph[current][edge_index++];
if (!edge.alive || dfs_index[edge.to] != -1) continue;
parent_vertex[edge.to] = current;
dfs_index[edge.to] = int(vertex.size());
vertex.push_back(edge.to);
stack.emplace_back(edge.to, 0);
}
int reachable_count = int(vertex.size());
std::vector<std::vector<int>> predecessor(reachable_count);
for (int from : vertex) {
for (const auto& edge : graph[from]) {
if (!edge.alive || dfs_index[edge.to] == -1) continue;
predecessor[dfs_index[edge.to]].push_back(dfs_index[from]);
}
}
std::vector<int> parent(reachable_count, -1);
for (int index = 1; index < reachable_count; ++index) {
parent[index] = dfs_index[parent_vertex[vertex[index]]];
}
std::vector<int> semi(reachable_count);
std::vector<int> idom(reachable_count, -1);
std::vector<int> ancestor(reachable_count, -1);
std::vector<int> label(reachable_count);
std::vector<std::vector<int>> bucket(reachable_count);
for (int index = 0; index < reachable_count; ++index) {
semi[index] = index;
label[index] = index;
}
auto compress = [&](int start) {
std::vector<int> path;
int current = start;
while (
ancestor[current] != -1 &&
ancestor[ancestor[current]] != -1
) {
path.push_back(current);
current = ancestor[current];
}
for (int index = int(path.size()) - 1; index >= 0; --index) {
int node = path[index];
int parent_node = ancestor[node];
if (semi[label[parent_node]] < semi[label[node]]) {
label[node] = label[parent_node];
}
ancestor[node] = ancestor[parent_node];
}
};
auto eval = [&](int node) {
if (ancestor[node] == -1) return label[node];
compress(node);
int parent_node = ancestor[node];
if (semi[label[parent_node]] < semi[label[node]]) {
return label[parent_node];
}
return label[node];
};
for (int current = reachable_count - 1; current >= 1; --current) {
for (int previous : predecessor[current]) {
semi[current] = std::min(semi[current], semi[eval(previous)]);
}
bucket[semi[current]].push_back(current);
ancestor[current] = parent[current];
int parent_node = parent[current];
for (int node : bucket[parent_node]) {
int best = eval(node);
idom[node] =
semi[best] < semi[node] ? best : parent_node;
}
bucket[parent_node].clear();
}
for (int current = 1; current < reachable_count; ++current) {
if (idom[current] != semi[current]) {
idom[current] = idom[idom[current]];
}
}
idom[0] = 0;
DominatorTree result;
result.root = root;
result.immediate_dominator.assign(n, -1);
result.children.assign(n, {});
result.dfs_order = vertex;
for (int index = 0; index < reachable_count; ++index) {
int current = vertex[index];
int dominator = vertex[idom[index]];
result.immediate_dominator[current] = dominator;
if (current != root) result.children[dominator].push_back(current);
}
result.tin.assign(n, -1);
result.tout.assign(n, -1);
int timer = 0;
std::vector<std::pair<int, int>> tree_stack;
tree_stack.emplace_back(root, 0);
result.tin[root] = timer++;
while (!tree_stack.empty()) {
int current = tree_stack.back().first;
int& child_index = tree_stack.back().second;
if (child_index == int(result.children[current].size())) {
result.tout[current] = timer;
tree_stack.pop_back();
continue;
}
int child = result.children[current][child_index++];
result.tin[child] = timer++;
tree_stack.emplace_back(child, 0);
}
return result;
}
} // namespace graph
} // namespace m1une