『图论』有向图强连通分量的Tarjan算法
在图论中,一个有向图被成为是强连通的(strongly connected)
当且仅当每一对不相同结点u
和v
间既存在从u
到v
的路径也存在从v
到u
的路径。有向图的极大强连通子图(这里指点数极大)被称为强连通分量(strongly connected component)
。
比如说这个有向图中,点\(1,2,4,5,6,7,8\)和相应边组成的子图就是一个强连通分量,另外点\(3,9\)单独构成强连通分量。
Tarjan
算法是由Robert Tarjan
提出的用于寻找有向图的强连通分量的算法。它可以在\(O(|V|+|E|)\)的时间内得出结果。
Tarjan
算法主要是利用DFS
来寻找强连通分量的。在介绍该算法之前,我们先来介绍一下搜索树。先前那个有向图的搜索树是这样的:
有向图的搜索树主要有\(4\)种边(这张图只有\(3\)种),其中用实线画出来的是树边(tree edge)
,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。用长虚线画出来的是反祖边(back edge)
,也被叫做回边。用短虚线画出来的是横叉边(cross edge)
,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点并不是当前节点的祖先时形成的。除此之外,像从结点\(1\)到结点\(6\)这样的边叫做前向边(forward edge)
,它是在搜索的时候遇到子树中的结点的时候形成的。
现在我们来看看在DFS
的过程中强连通分量有什么性质。
很重要的一点是如果结点u
是某个强连通分量在搜索树中遇到的第一个结点(这通常被称为这个强连通分量的根),那么这个强连通分量的其余结点肯定是在搜索树中以u
为根的子树中。如果有个结点v
在该强连通分量中但是不在以u
为根的子树中,那么u
到v
的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和u
是第一个访问的结点矛盾了。
Tarjan
算法主要是在DFS
的过程中维护了一些信息:DFN
、LOW
和一个栈。
- 栈里的元素表示的是当前已经访问过但是没有被归类到任一强连通分量的结点。
- \(DFN[u]\)表示结点
u
在DFS
中第一次搜索到的次序,通常被叫做时间戳。 - \(LOW[u]\)稍微有些复杂,它表示从
u
或者以u
为根的子树中的结点,再通过一条反祖边或者横叉边可以到达的时间戳最小的结点v
的时间戳,并且要求v
有一些额外的性质:v
还要能够到达u
。显然通过反祖边到达的结点v
满足LOW
的性质,但是通过横叉边到达的却不一定。
可以证明,结点u
是某个强连通分量的根等价于\(DFN[u]\)和\(LOW[u]\)相等。简单可以理解成当它们相等的时候就不可能从u
通过子树再经过其它时间戳比它小的结点回到u
。
当通过u
搜索到一个新的节点v
的时候可以有多种情况:
结点
u
通过树边到达结点v
。\(LOW[u]=\min(LOW[u],LOW[v])\)
结点
u
通过反祖边到达结点v
,或者通过横叉边到达结点v
并且满足LOW
定义中v
的性质,\(LOW[u]=\min(LOW[u],DFN[v])\)
在Tarjan
算法进行 DFS
的过程中,每离开一个结点,我们就判断一下LOW
是否小于DFN
,如果是,那么着个结点可以到达它先前的结点再通过那个结点回到它,肯定不是强连通分量的根。如果DFN
和LOW
相等,那么就不断退栈直到当前结点为止,这些结点就属于一个强连通分量。
至于如何更新LOW
,关键就在于第二种情况,当通过反祖边或者横叉边走到一个结点的时候,只需要判断这个结点是否在栈中,如果在就用它的LOW
值更新当前节点的LOW
值,否则就不更新。因为如果不在栈中这个结点就已经确定在某个强连通分量中了,不可能回到u
。
现在我们对着先前的图模拟一次。结点内的标号就是DFN
值,结点边上的标号是表示LOW
值,当前所在的结点用灰色表示。
首先从第一个结点开始进行搜索,最初LOW[1]=1
。此时栈里的结点是\(1\)。
然后到达第二个结点,同时也初始化LOW[2]=2
。此时栈里的结点是\(1,2\)。
类似地,到达第三个结点,同时也初始化LOW[3]=3
。此时栈里的结点是\(1,2,3\)。
此时结点3
没有其余边可以继续进行搜索了,我们需要离开它了,因为发现DFN[3]=LOW[3]
,所以结点3
是一个强连通分量的根,出栈直到结点3
为止,得到刚好只有一个结点3
的强连通分量。此时栈里的结点是\(1,2\)。
从结点3
返回后到结点2
,而后进入结点4
,从结点4
可以到达结点1
,但是结点1
已经访问过了,并且是通过反祖边,更新LOW[4]
的值。此时栈里的结点是\(1,2,4\)。
继续从结点4
还可以通过横叉边到达结点3
,但是结点3
并不在栈中(也就是结点3
并没有路径到达结点4
),不做任何改动。此时栈里的结点是\(1,2,4\)。
接着一直搜索直到结点6
。此时栈里的结点是\(1,2,4,5,6\)。
从结点6
出发可以通过横叉边到达结点4
,因为它已经访问过而且还在栈中,更新LOW[6]
。此时栈里的结点是\(1,2,4,5,6\)。
接着回退到结点5
,使用结点6
的值更新LOW[5]
。此时栈里的结点是\(1,2,4,5,6\)。
从结点5
出发经过结点7
后到达结点8
。遇到反祖边回到结点5
更新LOW[8]
。此时栈里的结点是\(1,2,4,5,6,7,8\)。
继续到达结点9
。此时栈里的结点是\(1,2,4,5,6,7,8,9\)。
离开时发现DFN[9]=LOW[9]
。找到强连通分量,出栈。此时栈里的结点是\(1,2,4,5,6,7,8\)。
回到结点8
,此时LOW[8]<DFN[8]
,不做处理继续回退。
直到回到结点1
的时候LOW[1]=DFN[1]
。此时栈里的结点是\(1,2,4,5,6,7,8\)。一直退栈直到遇见1
,找到强连通分量\(1,2,4,5,6,7,8\)。
代码实现:
inline void tarjan(int u) {
DFN[u]=LOW[u]=++Time;//Time表示时间戳
stack[u]=1;//stack[u]表示结点u是否仍然在栈中
stack[top++]=u;//top表示栈顶位置
for (int k=head[u]; k; k=next[k])
{
int v=point[k];
if (!DFN[v]) {//树边的情况
tarjan(v);
if (LOW[v]<LOW[u]) LOW[u]=LOW[v];
} else if(stack[v] && DFN[v]<LOW[u]) LOW[u]=DFN[v];//横叉边或者反祖边的情况
}
if (LOW[u]==DFN[u]) {
++cnt;//表示强连通分量的个数
tmp=0;
while (tmp!=u) {
tmp=stack[--top];
belong[tmp]=cnt;//belong[u]表示结点u属于那一个强连通分量
stack[tmp]=0;
}
}
}
『图论』有向图强连通分量的Tarjan算法的更多相关文章
- 有向图强连通分量的Tarjan算法
有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...
- 算法笔记_144:有向图强连通分量的Tarjan算法(Java)
目录 1 问题描述 2 解决方案 1 问题描述 引用自百度百科: 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连 ...
- 【转】有向图强连通分量的Tarjan算法
原文地址:https://www.byvoid.com/blog/scc-tarjan/ [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly con ...
- 【转载】有向图强连通分量的Tarjan算法
转载地址:https://www.byvoid.com/blog/scc-tarjan [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly conn ...
- 有向图强连通分量的Tarjan算法(转)
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...
- 有向图强连通分量的Tarjan算法及模板
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强联通(strongly connected),如果有向图G的每两个顶点都强联通,称有向图G是一个强联通图.非强联通图有向 ...
- Java实现有向图强连通分量的Tarjan算法
1 问题描述 引用自百度百科: 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为 ...
- 强连通分量的Tarjan算法
资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...
- 有向图强连通分量的Tarjan算法和Kosaraju算法
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...
随机推荐
- Git设置分支保护实现CodeReview卡点
# Git设置分支保护实现CodeReview卡点 > From:https://blog.csdn.net/crisschan/article/details/100922668 > G ...
- python编程基础之三十三
构造方法: 目的:构造方法用于初始化对象,可以在构造方法中添加成员属性 触发时机:实例化对象的时候自动调用 参数:第一个参数必须是self,其它参数根据需要自己定义 返回值:不返回值,或者说返回Non ...
- ConcurrentHashMap实现原理以及源码分析
ConcurrentHashMap是HashMap的高并发版本,是线程安全的,而HashMap是非线程安全的 一.底层实现 底层结构跟hashmap一样,都是通过数组+链表+红黑树实现的,不过它要保证 ...
- 为什么那么多自学JAVA的后来都放弃了?总结起来就这些原因
目前信息化产业发展势头很好,互联网就成为了很多普通人想要涉及的行业,因为相比于传统行业,互联网行业涨薪幅度大,机会也多,所以就会大批的人想要转行来学习Java开发. 目前来讲市场上需要的Java人员非 ...
- 阿里云学生服务器+WordPress搭建个人博客
搭建过程: 第一步:首先你需要一台阿里云服务器ECS,如果你是学生,可以享受学生价9.5元/月 (阿里云翼计划:https://promotion.aliyun.com/ntms/act/campus ...
- python selenium之CSS定位
ccs的优点:css相对xpath语法比xpath简洁,定位速度比xpath快 css的缺点:css不支持用逻辑运算符来定位,而xpath支持.css定位语法形式多样,相对xpath比较难记. css ...
- Hadoop(MapR)分布式安装及自动化脚本配置
MapR的分布式集群安装过程还是很艰难的,远远没有计划中的简单.本人总结安装配置,由于集群有很多机器,手动每台配置是很累的,编写了一个自动化配置脚本,下面以脚本为主线叙述(脚本并不完善,后续继续完善中 ...
- [Luogu4550] 收集邮票
题目描述 有n种不同的邮票,皮皮想收集所有种类的邮票.唯一的收集方法是到同学凡凡那里购买,每次只能买一张,并且买到的邮票究竟是n种邮票中的哪一种是等概率的,概率均为1/n.但是由于凡凡也很喜欢邮票,所 ...
- Libevent::evhttp服务器
#include <cstdio> #include <stdio.h> #include <stdlib.h> #include <string.h> ...
- eclipse提交代码到GitHub
1.配置git 2.右键项目--> Team--> Share Project... 3.右键项目--> Team--> Commit...