在图论中,一个有向图被成为是强连通的(strongly connected)当且仅当每一对不相同结点uv间既存在从uv的路径也存在从vu的路径。有向图的极大强连通子图(这里指点数极大)被称为强连通分量(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为根的子树中,那么uv的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和u是第一个访问的结点矛盾了。

Tarjan算法主要是在DFS的过程中维护了一些信息:DFNLOW和一个栈。

  • 栈里的元素表示的是当前已经访问过但是没有被归类到任一强连通分量的结点。
  • \(DFN[u]\)表示结点uDFS中第一次搜索到的次序,通常被叫做时间戳。
  • \(LOW[u]\)稍微有些复杂,它表示从u或者以u为根的子树中的结点,再通过一条反祖边或者横叉边可以到达的时间戳最小的结点v的时间戳,并且要求v有一些额外的性质:v还要能够到达u。显然通过反祖边到达的结点v满足LOW的性质,但是通过横叉边到达的却不一定。

可以证明,结点u是某个强连通分量的根等价于\(DFN[u]\)和\(LOW[u]\)相等。简单可以理解成当它们相等的时候就不可能从u通过子树再经过其它时间戳比它小的结点回到u

当通过u搜索到一个新的节点v的时候可以有多种情况:

  1. 结点u通过树边到达结点v

    \(LOW[u]=\min(LOW[u],LOW[v])\)

  2. 结点u通过反祖边到达结点v,或者通过横叉边到达结点v并且满足LOW定义中v的性质,

    \(LOW[u]=\min(LOW[u],DFN[v])\)

Tarjan算法进行 DFS的过程中,每离开一个结点,我们就判断一下LOW是否小于DFN,如果是,那么着个结点可以到达它先前的结点再通过那个结点回到它,肯定不是强连通分量的根。如果DFNLOW相等,那么就不断退栈直到当前结点为止,这些结点就属于一个强连通分量。

至于如何更新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算法的更多相关文章

  1. 有向图强连通分量的Tarjan算法

    有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...

  2. 算法笔记_144:有向图强连通分量的Tarjan算法(Java)

    目录 1 问题描述 2 解决方案 1 问题描述 引用自百度百科: 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连 ...

  3. 【转】有向图强连通分量的Tarjan算法

    原文地址:https://www.byvoid.com/blog/scc-tarjan/ [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly con ...

  4. 【转载】有向图强连通分量的Tarjan算法

    转载地址:https://www.byvoid.com/blog/scc-tarjan [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly conn ...

  5. 有向图强连通分量的Tarjan算法(转)

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

  6. 有向图强连通分量的Tarjan算法及模板

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强联通(strongly connected),如果有向图G的每两个顶点都强联通,称有向图G是一个强联通图.非强联通图有向 ...

  7. Java实现有向图强连通分量的Tarjan算法

    1 问题描述 引用自百度百科: 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为 ...

  8. 强连通分量的Tarjan算法

    资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...

  9. 有向图强连通分量的Tarjan算法和Kosaraju算法

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

随机推荐

  1. kubernetes垃圾回收器GarbageCollector源码分析(一)

    kubernetes版本:1.13.2 背景 由于operator创建的redis集群,在kubernetes apiserver重启后,redis集群被异常删除(包括redis exporter s ...

  2. 网页布局——table布局

    table 的特性决定了它非常适合用来做布局,并且表格中的内容可以自动居中,这是之前用的特别多的一种布局方式 而且也加入了 display:table;dispaly:table-cell 来支持 t ...

  3. 查询SQL SERVER 数据库版本号脚本语句

    数据库直接执行此语句即可select @@version 示例: Microsoft SQL Server 2014 - 12.0.2000.8 (X64)   Feb 20 2014 20:04:2 ...

  4. Maven插件构建Docker镜像

    背景 微服务架构下,微服务在带来良好的设计和架构理念的同时,也带来了运维上的额外复杂性,尤其是在服务部署和服务监控上.单体应用是集中式的,就一个单体跑在一起,部署和管理的时候非常简单,而微服务是一个网 ...

  5. MySQL复制从库建立-xtracebackup方式

    Percona XtraBackup工具提供了一种在系统运行时执行MySQL数据热备份的方法. Percona XtraBackup在事务系统上执行联机非阻塞,紧密压缩,高度安全的完整备份,因此在计划 ...

  6. lua多线程解决方案

    直观的讲:lua并不支持多线程,lua语言本身具有携程功能,但携程仅仅是一种中继器. lua多线程的目的:有并发需求时,共享一些数据. 例如使用lua写一个并发服务器.用户登陆之后,用户数据储存在lu ...

  7. java普通项目打包成可执行jar文件时如何添加第三包

    在java的web项目中,引用第三方包的时候非常简单.因为在web项目上中,默认有一个web-inf文件夹.web-inf文件夹下有一个lib文件夹,如果有用到第三方包,直接丢进去就行了.但是对于普通 ...

  8. Flash安全总结

    ActionScript AS是基于ECMAScript的语言,为了交互的需要flash应用引入ActionScript.ActionScript一共有三个版本,其中3.0较之前两个版本变化很大.Ac ...

  9. socat的介绍与使用

    Socat 是 Linux 下的一个多功能的网络工具,名字来由是 「Socket CAT」.其功能与有瑞士军刀之称的 Netcat 类似,可以看做是 Netcat 的加强版. Socat 的主要特点就 ...

  10. c++异常处理的方法

    c++异常处理 程序运行时常会碰到一些异常情况,例如:做除法的时候除数为 0:用户输入年龄时输入了一个负数:用 new 运算符动态分配空间时,空间不够导致无法分配:访问数组元素时,下标越界:打开文件读 ...