资料参考

强连通分量

强连通分量strongly connected component)是图论中的概念。图论中,强连通图指每一个顶点皆可以经由该图上的边抵达其他的每一个点的有向图。意即对于此图上每一个点对(Va,Vb),皆存在路径Va→Vb以及Vb→Va。(若有向图D = <V,E>的基图是连通图,则称D为弱连通图,简称连通图。若对于图上的任一Va,Vb,Va→Vb与Vb→Va至少成立其一,则称D为单项连通图。强连通图一定是单向连通图,单项连通图一定是弱连通图。)强连通分量则是指一张有向图G的极大强连通子图G'。如果将每一个强连通分量缩成一个点,则原图G将会变成一张有向无环图。一张图被称为有向无环图当且仅当此图不具有点集合数量大于一的强连通分量,因为有向环即是一个强连通分量,而且任何的强连通分量皆具有至少一个有向环。

比如说这个有向图中,点 1,2,4,5,6,7,8 和相应边组成的子图就是一个强连通分量,另外点3,9 单独构成强连通分量

Tarjan 算法是由 Robert Tarjan 提出的用于寻找有向图的强连通分量的算法。它可以在O(|V|+|E|)的时间内得出结果。(运行Tarjan算法的过程中可以知道,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(|V|+|E|)

搜索树

Tarjan 算法主要是利用 DFS 来寻找强连通分量的。在介绍该算法之前,我们先来介绍一下搜索树。先前那个有向图的搜索树是这样的:

有向图的搜索树主要有 4 种边(这张图只有 3 种),其中用实线画出来的是树边(tree edge),每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。用长虚线画出来的是反祖边(back edge),也被叫做回边。用短虚线画出来的是横叉边(cross edge),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点并不是当前节点的祖先时形成的。除此之外,像从结点1到结点6这样的边叫做前向边(forward edge),它是在搜索的时候遇到子树中的结点的时候形成的,容易知道移除前向边不会改变图的连通性,所以在讨论中基本忽略它。另外,还有从一棵搜索树上的节点到另一棵搜索树上的节点的边,称为跨树边,但是跨树边在这里也发挥不了大的作用。

Tarjan算法

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

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

  • 栈里的元素表示的是当前已经访问过但是没有被归类到任一强连通分量的结点。
  • dfn[u] 表示结点 u 在 DFS 中第一次搜索到的次序,通常被叫做时间戳。
  • Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号(即最小的dfn值)(实际操作中low[i]不一定最小,但不会影响程序的最终结果)(另外一种关于low[u]的表述:记录该点所在的强连通子图所在搜索子树的根节点的Dfn值)
  • 结点 u 是某个强连通分量的根等价于 dfn[u] 和 low[u] 相等。简单可以理解成当它们相等的时候就不可能从 u 通过子树再经过其它时间戳比它小的结点回到 u。(dfn[u] == low[u], 则u是分量子树的树根。 这是一个充要条件。 -->: 如果u 是树根,且由于是强分量,其子孙都可达到u, 则子孙的low 都是u 的编号。则low[u] = dfn[u].    <--: 如果low[u] == dfn[u], 假设u不是树根,那么肯定有u的祖先是树根,那么low[u]肯定比当前值小,矛盾,因此u是树根。)

算法伪代码

tarjan(u)
{
    DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
    Stack.push(u)                              // 将节点u压入栈中
    for each (u, v) in E                       // 枚举每一条边
        if (v is not visted)               // 如果节点v未被访问过
            tarjan(v)                  // 继续向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in S)                   // 如果节点v还在栈内
            Low[u] = min(Low[u], DFN[v])
    if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
        repeat
            v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
            print v
        until (u== v)
}

该算法有三个值得注意的地方:

  • Low[u] = min(Low[u], DFN[v])当搜索到一条u->v的边,若v仍在栈中,则v必为u的祖先,u->v为一条后向边,由于形成后向边,则必构成环,该环中的所有点必为一个强联通分量或强连通分量中的某些点,对于后向边,我们需要对Low进行更新,因为Low记录的是能够追溯到的最早的栈中节点的次序号,所以 Low[u] = min(Low[u], DFN[v])。
  • 对于横跨边u->v,是不需要对Low[u]进行更新的。若一个节点已经访问过,则它的DFN必有一个值,而当v已经被访问过,但已经不在栈中(已经是一个强连通分量中的点),则u,v不在同一DFST上,此时构成了横跨边,没有祖先孙子关系,不需要对Low值进行更新。
  • 回溯的时候,对树枝边的Low[u]就行更新,Low[u] = min(Low[u], Low[v])

Tarjan算法的操作原理如下:

  • Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。
  • 可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。
  • 这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。
  • 强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。
  • 如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。

大致证明:

  • 在栈里,当dfs遍历到v,而且已经遍历完v所能直接到达的顶点时,low[v]=dfn[v]时,v一定能到达栈里v上面的顶点:因为当dfs遍历到v,而且已经dfs递归调用完v所能直接到达的顶点时(假设上面没有low=dfn),这时如果发现low[v]=dfn[v],栈上面的顶点一定是刚才从顶点v递归调用时进栈的,所以v一定能够到达那些顶点。
  • dfs遍历时,如果已经遍历完v所能直接到达的顶点而low[v]=dfn[v],我们知道v一定能到达栈里v上面的顶点,这些顶点的low一定小于 自己的dfn,不然就会出栈了,也不会小于dfn[v],不然low [v]一定小于dfn[v],所以栈里v以其v以上的顶点组成的子图是一个强连通分量,如果它不是极大强连通分量的话low[v]也一定小于dfn[v](这里不再详细说),所以栈里v以其v以上的顶点组成的子图是一个极大强连通分量。
  • 若存在边<i, j>且遍历到它的时候j在栈中,那么i和j可能存在三种关系:
  • (1)i是j的祖先;
  • (2)j是i的祖先;
  • (3)i和j无前后关系。对于情况(1),必有dfn[j]>dfn[i],因此不必考虑;

对于情况(2),<i, j>是逆向边,显然i、j处于同一个强连通分支;
对于情况(3):<i, j>是横叉边。显然i、j必然在同一棵搜索树中(因为搜索树的根结点肯定满足low=dfn),设p=LCA(i, j),由于从p到j的路径上木有low=dfn的结点(否则j已经出栈了),所以j必然可以到达p,又因为p可以到达i,所以j也可以到达i,又因为存在边<i, j>,所以i、j处于同一个强连通分支,这样就需要在计算low[i]的时候把dfn[j]考虑进去,而不能让i及其所有后代成为一个强连通分支。

C++代码

/* 寻找有向图强连通分量的tarjan算法
 * index表示的就是时间戳
 * scc_cnt 表示强连通分量的个数
 * belong[u] 表示结点u属于那一个强连通分量
 * inst[u] 表示结点u是否仍然在栈中
 * st[] 和 top 分辨表示栈和栈顶位置
 *index = scc_cnt = top = 0*/
void targin(int u)
{
	int v;
	dfn[u] = low[u] = ++index;
	st[++top] = u;
	inst[u] = 1;
	for (int i = head[u];i != -1;i = edge[i].next)
	{
		v = edge[i].v;
		if (!dfn[v])
		{
			targin(v);
			low[u] = min(low[u],low[v]);
		}
		else if (inst[v])
			low[u] = min(low[u],dfn[v]);
	}
	if (low[u] == dfn[u])
	{
		scc_cnt++;
		do
		{
			v = st[top--];
			inst[v] = 0;
			belong[v] = scc_cnt;
		}
		while (u != v);
	}
}

自我检测

强连通分量的Tarjan算法的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. 『图论』有向图强连通分量的Tarjan算法

    在图论中,一个有向图被成为是强连通的(strongly connected)当且仅当每一对不相同结点u和v间既存在从u到v的路径也存在从v到u的路径.有向图的极大强连通子图(这里指点数极大)被称为强连 ...

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

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

  8. 【强连通分量】tarjan算法及kosaraju算法+例题

    阅读前请确保自己知道强连通分量是什么,本文不做赘述. Tarjan算法 一.算法简介 Tarjan算法是一种由Robert Tarjan提出的求有向图强连通分量的时间复杂度为O(n)的算法. 首先我们 ...

  9. 寻找图的强连通分量:tarjan算法简单理解

    1.简介tarjan是一种使用深度优先遍历(DFS)来寻找有向图强连通分量的一种算法. 2.知识准备栈.有向图.强连通分量.DFS. 3.快速理解tarjan算法的运行机制提到DFS,能想到的是通过栈 ...

随机推荐

  1. CentOS7 下安装JDK1.7 和 Tomcat7

    一.下载JDK 和 Tomcat安装包 1.JDK1.7 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downl ...

  2. 完美演绎DevExpress XtraPrinting Library 的打印功能

    完美演绎DevExpress XtraPrinting Library 的打印功能 2010-05-14 17:40:49|  分类: 默认分类|字号 订阅     设计报告不仅费时间,而且还乏味!但 ...

  3. cookie记住密码功能

    很多门户网站都提供了记住密码功能,虽然现在的浏览器都已经提供了相应的记住密码功能 效果就是你每次进入登录页面后就不需要再进行用户名和密码的输入: 记住密码功能基本都是使用cookie来进行实现的,因此 ...

  4. unix环境高级编程基础知识之第四章

    1.从当前用户转到root用户:直接输入su命令,然后输入root密码,如果之前没有设置root命令密码会登陆不成功,这里需要命令sudo passwd命令设置密码,然后按照上面输入就成:从root命 ...

  5. 迭代器模式的一种应用场景以及C#对于迭代器的内置支持

    迭代器模式 先放上gof中对于迭代器模式的介绍镇楼 意图 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示. 别名 游标(Cursor). 动机 一个聚合对象, 如列表(li ...

  6. 【MVVMLight小记】一.快速搭建一个基于MVVMLight的silverlight小程序

    写了篇MVVM小记http://www.cnblogs.com/whosedream/p/mvvmnote1.html,说好要写点MVVMLight的东西,所以接着写,以便和大家共勉. 我假设你已经有 ...

  7. ASP.NET MVC3入门教程之第一个WEB应用程序

    本文转载自:http://www.youarebug.com/forum.php?mod=viewthread&tid=91&extra=page%3D1 上一节,我们已经搭建好了AS ...

  8. 【Python】[函数式编程]高阶函数,返回函数,装饰器,偏函数

    函数式编程高阶函数 就是把函数作为参数的函数,这种抽象的编程方式就是函数式编程.--- - -跳过,不是很理解,汗 - ---

  9. MySQL Workbench使用及教程

    MySQL Workbench是一款专为MySQL设计的ER/数据库建模工具.它是著名的数据库设计工具DBDesigner4的继任者.你可以用MySQL Workbench设计和创建新的数据库图示,建 ...

  10. 《Android性能优化》学习笔记链接<转载>

    今天找到一博文汇总了 Android性能优化 比较好的文章 ,本计划全看完,自己再精简下,因篇幅太长,先收藏了,等有时间 再仔细拜读,总结自己的看法:  第一季: http://www.csdn.ne ...