[Tarjan系列] Tarjan算法与有向图的SCC
前面的文章介绍了如何用Tarjan算法计算无向图中的e-DCC和v-DCC以及如何缩点。
本篇文章资料参考:李煜东《算法竞赛进阶指南》
这一篇我们讲如何用Tarjan算法求有向图的SCC( 强连通分量 )已经如何缩点。
给定一张有向图,若对于图中任意两个节点x和y,
既有x到y的路径,又有y到x的路径,则该有向图是一张“强连通图”。
有向图的极大连通子图被称为“强连通分量”,即SCC。
一个环一定是强连通图。如果既有x到y的路径,又有y到x的路径,那么x和y就一定在一个环中。
这就是Tarjan算法的原理:对于每个点x,找到与它一起能构成环的所有点。
下面介绍有向图中的三种边(x,y):
1. 树枝边:搜索树中x是y的父节点
2. 前向边:搜索树中x是y的祖先节点
3. 后向边:搜索树中y是x的祖先节点
4. 横叉边:除了以上三种情况外的边,满足dfn[y]<dfn[x]
这里只给出简单定义,不再赘述。
我们可以发现,用Tarjan算法求SCC时,后向边(x,y)可以和搜索树上从y到x的路径构成一个环。
除后向边外,通过横叉边也可能找到一条从y出发能回到x的祖先节点的路径。
那么为了找到通过横叉边和后向边构成的环,Tarjan算法在dfs的过程中维护一个栈,当访问到节点x时,栈中需要保存以下两类节点:
1. 搜索树上x的祖先节点,记为集合anc(x)。设y∈anc(x),若存在一条后向边(x,y),则(x,y)和y到x之间的路径一起形成环。
2. 已经访问过,并且存在一条路径到达anc(x)的节点。
设z时一个这样的点,从z出发存在一条路径到达y∈anc(x)。若存在横叉边(x,y),则(x,z)、z到y的路径、y到x的路径形成一个环。
综上,栈中的节点就是能从x出发点的“后向边”和“横叉边”形成环的节点。
至此,我们引入追溯值low[x]的概念,有向图的Tarjan算法里面的定义和无向图是不一样的。
还是设subtree(x)表示以x为根的子树。x的追溯值low[x]定义为满足一下条件的节点的最小dfn:
1. 该点在栈中 2. 存在一条从subtree(x)出发的有向边,以该点为终点
根据以上定义,Tarjan算法根据以下步骤计算low[x]:
1. 当节点x第一次被访问时,将x入栈,初始化low[x]=dfn[x]
2. 扫描从头x出发的每条边(x,y),若y没被访问过,则说明(x,y)时树枝边,递归访问y,从y回溯之后,令low[x]=min(low[x],low[y]),若y被访问过且y在栈中,令low[y]=min(low[x],dfn[y])
3. 从x回溯之前,判断是否有low[x]=dfn[x],若成立,则不断从栈中弹出节点直至x出栈。
SCC的判定法则:
在上面的计算步骤3中,从栈中从x到栈顶的所有节点构成一个SCC。
少废话,上代码!
好der~
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
inline int read(){
int data=,w=;char ch=;
while(ch!='-' && (ch<''||ch>''))ch=getchar();
if(ch=='-')w=-,ch=getchar();
while(ch>='' && ch<='')data=data*+ch-'',ch=getchar();
return data*w;
}
struct Edge{
int nxt,to;
#define nxt(x) e[x].nxt
#define to(x) e[x].to
}e[N<<];
int head[N],tot=;
inline void addedge(int f,int t){
nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
}
int dfn[N],low[N],stk[N],ins[N],c[N];
vector<int> scc[N];
int n,m,cnt,top,num;
void tarjan(int x){
dfn[x]=low[x]=++cnt;
stk[++top]=x,ins[x]=;
for(int i=head[x];i;i=nxt(i)){
int y=to(i);
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);//搜索树上的点
}else if(ins[y])
low[x]=min(low[x],dfn[y]);//y在栈中且y被访问过了
}
if(dfn[x]==low[x]){
num++;int z;//新的一个SCC
do{
z=stk[top--],ins[z]=;//弹出栈顶元素z
c[z]=num,scc[num].push_back(z);//z插入存第num个SCC的vector里
}while(z!=x);//直到x被弹出栈
}
}
int main(){
n=read();m=read();
for(int i=;i<=m;i++){
int x=read(),y=read();
addedge(x,y);
}
for(int i=;i<=n;i++)
if(!dfn[i])tarjan(i);
for(int i=;i<=num;i++){
printf("%d:",i);
for(int j=;j<scc[i].size();j++){
printf(" %d",scc[i][j]);
}
putchar();
}
return ;
}
SCC的缩点就非常简单了,上面我们已经用c[x]储存了每个点所在的SCC的编号,那我们直接类似e-DCC的缩点,把每个SCC缩成一个点,若c[x]≠c[y],我们就在编号为c[x]和c[y]的SCC中连一条边就可以得到一个有向无环图( DAG )。
代码真的非常简单,甚至不需要再跑一遍dfs。
给出代码:
for(int x=;x<=n;x++)
for(int i=head[x];i;i=nxt(i)){
int y=to(i);
if(c[x]==c[y])continue;
addedge_c(c[x],c[y]);
}
//够简单了吧...
整个程序的代码我就不贴出来了,建新图和我前面e-DCC缩点的博客完全一致。
下一篇讲点数学,别忘了来听课。
[Tarjan系列] Tarjan算法与有向图的SCC的更多相关文章
- [Tarjan系列] Tarjan算法求无向图的桥和割点
RobertTarjan真的是一个传说级的大人物. 他发明的LCT,SplayTree这些数据结构真的给我带来了诸多便利,各种动态图论题都可以用LCT解决. 而且,Tarjan并不只发明了LCT,他对 ...
- [Tarjan系列] Tarjan算法求无向图的双连通分量
这篇介绍如何用Tarjan算法求Double Connected Component,即双连通分量. 双联通分量包括点双连通分量v-DCC和边连通分量e-DCC. 若一张无向连通图不存在割点,则称它为 ...
- Tarjan算法求有向图强连通分量并缩点
// Tarjan算法求有向图强连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> #inc ...
- [Tarjan系列] 无向图e-DCC和v-DCC的缩点
上一篇讲了如何应用Tarjan算法求出e-DCC和v-DCC. 那么这一篇就是e-DCC和v-DCC的应用之一:缩点. 先讲e-DCC的缩点. 我们把每一个e-DCC都看成一个节点,把所有桥边(x,y ...
- Kosaraju 算法检测有向图的强连通性
给定一个有向图 G = (V, E) ,对于任意一对顶点 u 和 v,有 u --> v 和 v --> u,亦即,顶点 u 和 v 是互相可达的,则说明该图 G 是强连通的(Strong ...
- HDU 1269 -- 迷宫城堡【有向图求SCC的数目 && 模板】
迷宫城堡 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...
- 【数据结构与算法】多种语言(VB、C、C#、JavaScript)系列数据结构算法经典案例教程合集目录
目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 2. 专栏地址 「 刘一哥与GIS的故事 」之<数据结构与算法> 3. 专栏目录 [经典回放]多种语言系列数据结构算法 ...
- Tarjan系列算法总结(hdu 1827,4612,4587,4005)
tarjan一直是我看了头大的问题,省选之前还是得好好系统的学习一下.我按照不同的算法在hdu上选题练习了一下,至少还是有了初步的认识.tarjan嘛,就是维护一个dfsnum[]和一个low[],在 ...
- tarjan算法-解决有向图中求强连通分量的利器
小引 看到这个名词-tarjan,大家首先想到的肯定是又是一个以外国人名字命名的算法.说实话真的是很佩服那些算法大牛们,佩服得简直是五体投地啊.今天就遇到一道与求解有向图中强连通分量的问题,我的思路就 ...
随机推荐
- Hbase入门(三)——数据模型
Hbase最核心但也是最难理解的就是数据模型,由于与传统的关系型数据库不同,虽然Hbase也有表(Table),也有行(Row)和列(Column),但是与关系型数据库不同的是Hbase有一个列族(C ...
- 基于Docker搭建大数据集群(一)Docker环境部署
本篇文章是基于Docker搭建大数据集群系列的开篇之作 主要内容 docker搭建 docker部署CentOS 容器免密钥通信 容器保存成镜像 docker镜像发布 环境 Linux 7.6 一.D ...
- Spring框架(三)
对象依赖关系 Spring中,如何给对象的属性赋值? [DI, 依赖注入] 1) 通过构造函数 2) 通过set方法给属性注入值 3) p名称空间 4)自动装配(了解) 5) 注解 代码示例: &l ...
- 阿里云服务器ecs配置之安装redis服务
一.介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多,包括st ...
- 中缀表达式转后缀表达式(Java代码实现)
后缀表达式求值 后缀表达式又叫逆波兰表达式,其求值过程可以用到栈来辅助存储.例如要求值的后缀表达式为:1 2 3 + 4 * + 5 -,则求值过程如下: 遍历表达式,遇到数字时直接入栈,栈结构如下 ...
- Linux之修改系统密码
目录 Linux之修改系统密码 参考 RHEL6修改系统密码 RHEL7修改系统密码 Linux之修改系统密码
- CSS实现带箭头的提示框
我们在很多UI框架中看到带箭头的提示框,感觉挺漂亮,但是之前一直不知道其原理,今天网上找了些资料算是弄清楚原理了: 先上效果图: 原理分析: 上面的箭头有没有觉得很像一个三角形,是的,它就是三角形:只 ...
- java中的String是不可变类
String s = "hello "; s += "world"; 这两行代码执行后,原始的String对象中的内容没有变 在这段代码中,s原先指向一个Str ...
- [经验分享]C# 操作Windows系统计划任务
背景:我做了一个事情是要自己提前创建好很多要定时执行的任务,在我不在的时候自动执行这些程序,以保证我的工作能无人值守,那么我就需要建立系统计划任务来帮我完成这件事情,当然用脑子想想如何实现,很简单,每 ...
- Unity动态改变物体遮挡关系
在动态创建物体时,通常同父级下先创建的子物体会被后创建的遮挡,此时就需要我们用代码改变对象的层级. GameObject go;go.transform.SetAsLastSibling();//设置 ...