支配树与Lengauer-Tarjan算法

支配点

在一个有向图中,确定 S S S作为起点。对某个点 x x x而言,如果点 y y y x x x的支配点,则从 S S S x x x的任意路径均必须经过 y y y。显然支配点可能不止一个。但如果将 x x x的最近支配点到 x x x连一条边,则会形成一个树形结构,称之为支配树。

假设有图

digraph demo{
    1 -> {2}
    2 -> {3}
    3 -> {4, 5, 6}
    4 -> {1, 7}
    5 -> {3, 6}
    6 -> {8}
    7 -> {8, 9, 10}
}

且可视化如下

在这里插入图片描述

则其支配树是

在这里插入图片描述

dfs序与半支配点

S S S开始做一个深搜,依次进入的点的顺序即为dfs序。上图的dfs序为

1 2 3 4 7 8 9 10 5 6 

dfs序显然不是唯一的,一般而言,书面表示还会尽量写成升序形式以方便阅读。

x x x节点的半支配点 y y y是指: 1. 从 y y y x x x有一条路径,其中间点(不包括 x x x y y y本身)的 d f s dfs dfs序均大于 x x x d f s dfs dfs序;2. 且 y y y d f s dfs dfs序是所有满足上述条件的点中最小的那个。半支配点显然是唯一的。

在上述例子中, { 1 , 2 , 3 , 4 , 5 } \{1,2,3,4,5\} {1,2,3,4,5}是一个强连通分量,考虑其到8的路径,显然只有

3 -> 6 -> 8
3 -> 5 -> 6 -> 8
5 -> 6 -> 8

这三条路径满足条件1,而其中节点 3 3 3 d f s dfs dfs序最小的。因此 8 8 8的半支配点是 3 3 3

根据半支配点的定义,很容易写出一个递归的写法:

  1. 对于 x x x节点的前驱 y y y,如果 D F N y < D F N x DFN_y\lt{DFN_x} DFNy<DFNx,则 y y y x x x的半支配点候选;
  2. 否则,考虑 y y y的所有 d f s dfs dfs序大于 x x x的祖先节点,这些祖先节点的半支配点是x的半支配点候选。

当然暴力维护是不行的,需要安排好迭代顺序与数据结构。

S d o m i Sdom_i Sdomi是i的当前的半支配点,即当前能够找到的符合条件的dfs序最小的点; m n i mn_i mni表示节点i的祖先中dfs序最小的节点, u n i i uni_i unii表示。

确定支配点

假设 y y y x x x的半支配点,同时 u u u y y y x x x路径上的一个点, u u u的半支配点 v v v d f s dfs dfs序是所有 y y y x x x路径上点中最小的,则 x x x的支配点有两种情况:

  1. v = = y v==y v==y, 则 x x x的支配点就是 y y y
  2. D F N v < D F N y DFN_v\lt{DFN_y} DFNv<DFNy,则 x x x的支配点就是 u u u的支配点。

可以证明,不会证明。

算法与代码

下述算法是核心算法,用于求取半支配点以及部分支配点(第一种情况)。

for i in range(N, 1): # 按照dfs序的逆序遍历
    u = Ord[i] # 排名第i位的节点记作u
    for v in ig[u]: # 遍历u的入点
        uni_query(v) # 对v做一个并查集的查询,
        if Dfn[Sdom[mn[v]]] < Dfn[Sdom[u]]: # mn[v]表示v的祖先中半支配点dfs序最小的那个祖先
            Sdom[u] = Sdom[mn[v]] # 更新半支配点
	uni[u] = Parent[u] # 并查集合并
	SdomTree[Sdom[u]].append(u) # Sdom[u]到u引一条边
	u = Parent[u]
	for v in SdomTree[u]:
		uni_query(v) # 再次做一个查询,主要是为了更新uni[v]
		# u是v的半支配点,mn[v]是v的半支配点dfs序最小的那个祖先,如果if成立就是上文中的第一种情况
		# 否则,暂时将Idom[v]记作mn[v]
		Idom[v] = u if u == Sdom[mn[v]] else mn[v]

洛谷支配树模板题代码如下。

#include <bits/stdc++.h>
using namespace std;

struct DominatorTree_Lengauer_Tarjan{

using vi = vector<int>;
using vvi = vector<vi>;

vvi g; // 1-indexed
vvi ig;

void init(int n){
    g.assign(n + 1, vi());
	ig.assign(n + 1, vi());
}

void mkEdge(int a, int b){
	g[a].push_back(b);
	ig[b].push_back(a);
}

int S;
vi Dfn; // Dfn[i]表示节点i的dfs序
vi Ord; // Ord[i]表示排名第i位的节点
vi Parent; // Parent[i]表示深搜树上i节点的父节点
int Stamp; 

vi uni; // 带权并查集的father数组
vi mn;  // mn[i]表示i的所有祖先中,半支配点的dfs序最小的那个祖先
vi Sdom; // Sdom[i]表示i的半支配点
vi Idom; // Idom[i]表示i的直接支配点

vvi SdomTree;

void Lengauer_Tarjan(int s){
	int n = this->g.size() - 1;

    /// 确定dfs序
	Dfn.assign(this->g.size(), Stamp = 0);
	Ord.assign(this->g.size(), 0);
	Parent.assign(this->g.size(), 0);
	dfs(S = s);

	/// 求解半支配点与支配点的中间答案
	Sdom.assign(this->g.size(), 0);
	uni.assign(this->g.size(), 0);
	mn.assign(this->g.size(), 0);
	SdomTree.assign(this->g.size(), vi());
	Idom.assign(this->g.size(), 0);
	calcSdom();

    /// 最后确定支配点
	for(int i=2;i<=n;++i){
		int u = Ord[i];
		if(Idom[u] ^ Sdom[u]){
			Idom[u] = Idom[Idom[u]];
		}
	}
	return;
}

void dfs(int u){
	Ord[Dfn[u] = ++Stamp] = u;
	for(auto v : g[u]){
		if(0 == Dfn[v]){
			Parent[v] = u;
			dfs(v);
		}		
	}
	return;
}

void calcSdom(){
	int n = this->g.size() - 1;
	for(int i=1;i<=n;++i) Sdom[i] = mn[i] = uni[i] = i;
	for(int i=n;i>=2;--i){
		int u = Ord[i]; // 排名第i位的节点是u
		for(int v : ig[u]){
			if(0 == Dfn[v]) continue;
			uni_query(v);
			if(Dfn[Sdom[mn[v]]] < Dfn[Sdom[u]]){
				Sdom[u] = Sdom[mn[v]];
			} 
		}
		uni[u] = Parent[u]; // 将u向上合并
		SdomTree[Sdom[u]].push_back(u);
		for(int v : SdomTree[u = Parent[u]]){
			uni_query(v);
			Idom[v] = (u == Sdom[mn[v]]) ? u : mn[v]; 
		}
		SdomTree[u].clear();
	}
	return;
}

/// 带权并查集的find操作
int uni_query(int u){
	if(u == uni[u]) return u;
	int ans = uni_query(uni[u]);
	/// mn[u]是通过mn[uni[u]]计算出来的
	if(Dfn[Sdom[mn[uni[u]]]] < Dfn[Sdom[mn[u]]]) mn[u] = mn[uni[u]];
	return uni[u] = ans;
}

};

int N, M;
DominatorTree_Lengauer_Tarjan D;

int main(){
#ifndef ONLINE_JUDGE
    freopen("1.txt", "r", stdin);
#endif
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin >> N >> M;
	D.init(N);
	for(int a,b,i=0;i<M;++i){
        cin >> a >> b;
		D.mkEdge(a, b);
	}
	D.Lengauer_Tarjan(1);
    vector<int> ans(N+1, 0);
	for(int i=N;i>=2;--i) ans[D.Idom[D.Ord[i]]] += ++ans[D.Ord[i]];
	++ans[1];
	for(int i=1;i<=N;++i) cout << ans[i] << " "; cout << endl;
    return 0; 
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者简介: Andrew W.Appel,美国普林斯顿大学计算机科学系教授,第26届ACM SIGPLAN-SIGACT程序设计原理年会大会执行主席,1998-1999年在贝尔实验室做研究工作。主要研究方向是计算机安全、编译器设计、程序设计语言等。 内容简介: 本书全面讲述了现代编译器的各个组成部分,包括词法分析、语法分析、抽象语法、语义检查、中间代码表示、指令选择、数据流分析、寄存器分配以及运行时系统等。全书分成两部分,第一部分是编译的基础知识,适用于第一门编译原理课程(一个学期);第二部分是高级主题,包括面向对象语言和函数语言、垃圾收集、循环优化、SSA(静态单赋值)形式、循环调度、存储结构优化等,适合于后续课程或研究生教学。书中专门为学生提供了一个用C语言编写的实习项目,包括前端和后端设计,学生可以在一学期内创建一个功能完整的编译器。   本书适用于高等院校计算机及相关专业的本科生或研究生,也可供科研人员或工程技术人员参考。 目录: 第一部分 编译基本原理 第1章 绪论 1 1.1 模块与接口 1 1.2 工具和软件 3 1.3 语言的数据结构 3 程序设计:直线式程序解释器 7 推荐阅读 9 习题 9 第2章 词法分析 10 2.1 词法单词 10 2.2 正则表达式 11 2.3 有限自动机 13 2.4 非确定有限自动机 15 2.4.1 将正则表达式转换为NFA 16 2.4.2 将NFA转换为DFA 18 2.5 Lex:词法分析器的生成器 20 程序设计:词法分析 22 推荐阅读 23 习题 23 第3章 语法分析 27 3.1 上下文无关文法 28 3.1.1 推导 29 3.1.2 语法分析 29 3.1.3 二义性文法 30 3.1.4 文件结束符 31 3.2 预测分析 32 3.2.1 FIRST集合和FOLLOW集合 33 3.2.2 构造一个预测分析器 35 3.2.3 消除左递归 36 3.2.4 提取左因子 37 3.2.5 错误恢复 37 3.3 LR分析 39 3.3.1 LR分析引擎 40 3.3.2 LR(0)分析器生成器 41 3.3.3 SLR分析器的生成 44 3.3.4 LR(1)项和LR(1)分析表 45 3.3.5 LALR(1)分析表 46 3.3.6 各类文法的层次 47 3.3.7 二义性文法的LR分析 47 3.4 使用分析器的生成器 48 3.4.1 冲突 49 3.4.2 优先级指导 50 3.4.3 语法和语义 53 3.5 错误恢复 54 3.5.1 用error符号恢复 54 3.5.2 全局错误修复 55 程序设计:语法分析 57 推荐阅读 58 习题 58 第4章 抽象语法 62 4.1 语义动作 62 4.1.1 递归下降 62 4.1.2 Yacc生成的分析器 62 4.1.3 语义动作的解释器 64 4.2 抽象语法分析 65 4.2.1 位置 67 4.2.2 Tiger的抽象语法 68 程序设计:抽象语法 71 推荐阅读 71 习题 72 第5章 语义分析 73 5.1 符号表 73 5.1.1 多个符号表 74 5.1.2 高效的命令式风格符号表 75 5.1.3 高效的函数式符号表 76 5.1.4 Tiger编译器的符号 77 5.1.5 函数式风格的符号表 79 5.2 Tiger编译器的绑定 79 5.3 表达式的类型检查 82 5.4 声明的类型检查 84 5.4.1 变量声明 84 5.4.2 类型声明 85 5.4.3 函数声明 85 5.4.4 递归声明 86 程序设计:类型检查 87 习题 87 第6章 活动记录 89 6.1 栈帧 90 6.1.1 帧指针 91 6.1.2 寄存器 92 6.1.3 参数传递 92 6.1.4 返回地址 94 6.1.5 栈帧内的变量 94 6.1.6 静态链 95 6.2 Tiger编译器的栈帧 96 6.2.1 栈帧描述的表示 98 6.2.2 局部变量 98 6.2.3 计算逃逸变量 99 6.2.4 临时变量和标号 100 6.2.5 两层抽象 100 6.2.6 管理静态链 102 6.2.7 追踪层次信息 102 程序设计:栈帧 103 推荐阅读 103 习题 103 第7章 翻译成中间代码 106 7.1 中间表示 106 7.2 翻译为中间语言 108 7.2.1 表达式的种类 108 7.2.2 简单变量 111 7.2.3 追随静态链 112 7.2.4 数组变量 113 7.2.5 结构化的左值 114 7.2.6 下标和域选择 114 7.2.7 关于安全性的劝告 115 7.2.8 算术操作 116 7.2.9 条件表达式 1

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值