强连通分量的三种算法分析

本文将介绍什么是强连通分量,求解强连通分量的三种算法Kosaraju算法、Tarjan算法、Garbow算法。因为算法的过程很容易理解,真正难的是如何理解算法的思想,写主这个的时候我也不定完全明白算法为什么这样,为什么这样做就可以,所以还涉及到相当一部分证明,希望总结完成之后能够有更深一层的了解。 **** **更新感想** 一定要真正搞明白了,可以通过去真正实现一下,要不以为明白了,我就是在实现的过程中发现自己之前的理解是错误的,在实现算法的过程是发现输出的结果不正确!!!坑死爹啊…………前前后后看这三个算法确实花费了不少时间,一定要多参考文件,检验自己理解是否正确的最好方法就是去实现,现在三个算法已经完成,但是很明显的还有很多地方要优化,Tarjan算法和Gabow算法居然比Kosaraju算法还要慢。。。。。。淡疼。。 继续优化,我也会尽力把这部分内容写清楚,希望能给以后遇见相同问题的同志们一个参考,在保证算法思想正确的同时说明白。先上完整代码[点这里!点这里!](https://github.com/tianzhuwei/-_-/tree/master/Introduction%20to%20Algorithms/Graph%20Algorithms/StronglyConnectedCompoents),写完了不容易啊,刚刚开始有被误导的意思。

添加算法介绍PPT文件,[点击下载](https://github.com/tianzhuwei/PPT/blob/master/%E6%B1%82%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%AE%97%E6%B3%95%E4%BF%AE%E5%A4%8D%E7%89%88%E6%9C%AC.pptx)

**基于luosong同学PPT修改,改正其中的一些错误,感谢!** 以下为原文 **** >In the mathematical theory of directed graphs, a graph is said to be strongly connected if every vertex is reachable from every other vertex. The strongly connected components of an arbitrary directed graph form a partition into subgraphs that are themselves strongly connected. It is possible to test the strong connectivity of a graph, or to find its strongly connected components, in linear time. >引自
STRONGLY-CONNECTED-COMPONENTS(G)
1 call DFS(G) to compute finishing times u:f for each vertex u
2 compute GT
3 call DFS(GT), but in the main loop of DFS, consider the vertices
  in order of decreasing u:f (as computed in line 1)
4 output the vertices of each tree in the depth-first forest formed in line 3 as a
separate strongly connected component
当我看完这个算法时,第一感觉是很简单,很容易看明白它的操作方法,只不过为什么两次DFS,一次对原图G取逆,就能得到强连通分量呢? Kosaraju算法的显著特征是,**第一,引用了有向图的逆图**;**第二,需要对图进行两次DFS**(一次在逆图上,一次在原图上)。而且这个算法 *依赖于一个事实*:一个**有向图的强连通分量与其逆图是一样的**(即假如顶点任意顶点s与t属于原图中的一个强连通分量,那么在逆图中这两个顶点必定也属于同一个强连通分量)。 下面开始证明: - 命题1:**该算法求出的都是强连通分量** **假设在转置后的图GT中,x用DFS可以搜索到y处**: 我们可以得到两个信息: 1. 可知在图**GT**中存在路径** X —> Y**。所以说明原图**G**中存在路径 **Y — > X**。 2. 另外一个信息就是x的序号在y之后(柱:指完成时间标记)。这有两种可能: **a**. 以y为根先DFS出了一棵搜索树,但是这棵子树里不包含x,并且此时x还未被dfs到。 **b**. y 是 x 扩展出来的搜索树中的一个结点。 **结合第一个条件,a 是不可能成立的(因为在原图中 Y 是可以到达 X 的),条件 b 也就证明了原图中X 是可以到达 Y 的,所以求得的为强连通分量,Kosaraju 算法得到证明。** 证明参考(http://blog.csdn.net/hechenghai/article/details/7273178)感谢。 原文中还有第二证明: - 命题2:**所有的极大强连通分量都会被该算法求到** 暂时未整理,有兴趣的可以先看原文。
以后尽量避免直接粘代码,一个显的low,另一个就是low。 主要是介绍思想以及个人遇到的总是总结
Tarjan算法

 用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示i所能直接或间接(很重要)达到时间最小的顶点。①(实际操作中low[i]不一定最小,但不会影响程序的最终结果)(很重要)
(稍后会上传我们一起学习时同学做的PPT文档修复版本,因为感觉他讲的思想不是很正确或者说那样理解起来不简单,可以参考。链接已放文章开头)

 其实,tarjan算法的基础是DFS。我们准备两个数组Low和Dfn。Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的Dfn值(很绕嘴,往下看你就会明白;柱:这句话很重要,细细品味),Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图(无需回溯(柱:这是原文的说法无须回溯,但是我理解上是需要回溯的))和对栈的操作,我们就可以得到该有向图的强连通分量。

  1. 数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
  2. 堆栈:每搜索到一个点,将它压入栈顶。
  3. 当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中,p的low值为两点的low值中较小的一个。
  4. 当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。
  5. 每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
    继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。

柱:上面红色字体的部分“如果此时”指的是什么时间??因为一开始理解错误,导致在实现算法时输出结果出现错误。也有注明说是dfn[p]时,如何理解?我们只记录每一顶点的访问时间,而不去记录完成时间,所以这里说的dfn[p]也就中是第一次访问到p结点时的时间,再次注意不是回溯时的时间!!!!我第一次就理解跑偏,导致做了很多无用功,再次强调下只是记录第一次的访问时间,而不考虑回溯时间。 这种说法就照应的了前文说过的①(实际操作中low[i]不一定最小,但不会影响程序的最终结果)(很重要) 这个就属于是Tarjan实现过程中的一些技巧了, 可以参考下文的算法思想来了解这部分,知道我们最终想要的是什么。

 可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(n+e)。
 由于每个顶点只访问过一次,每条边也只访问过一次,我们就可以在O(n+m)的时间内求出有向图的强连通分量。但是,这么做的原因是什么呢?

Tarjan算法的操作原理如下:

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

参考自http://www.cnblogs.com/saltless/archive/2010/11/08/1871430.html感谢

到这里才才算是明白了,下面的Gabow算法也是基于这个思想,只不过是实现过程中使用的“技巧”不一样罢了,恩恩“技巧”这个词很准确啊!


Garbow算法

 Gabow算法是Tarjan算法的提升版本,该算法类似于Tarjan算法。算法维护了一个顶点栈,但是还是用了第二个栈来确定何时从第一个栈中弹出各个强分支中的顶点,它的功能类似于Tarjan算法中的数组low。从起始顶点w处开始进行DFS过程中,当一条回路显示这组顶点都属于同一个强连通分支时,就会弹出栈二中顶点,只留下回边的目的顶点,也即搜索的起点w。
 当回溯到递归起始顶点w时(这里的起始顶点指的可不是那个根结点,而是每次递归调用结束刚刚弹出时的那个顶点),如果此时该顶点在栈二顶部,则说明该顶点是一个强联通分量的起始顶点,那么在该顶点之后搜索的顶点都属于同一个强连通分支。于是,从第一个栈中弹出这些点,形成一个强连通分支。

其实Tarjan算法和Gabow算法其实是同一个思想的不同实现,但是,Gabow算法更精妙,时间更少(不用频繁更新Low[])。

Gabow_Algorithm伪代码:

step1:

找一个没有被访问过的节点v,goto step2(v)。否则,算法结束。

step2(v):

将v压入堆栈Path栈和Root栈

对于v所有的邻接顶点u:

1) 如果没有访问过,则step2(u)

2) 如果访问过,但没有删除,维护Root栈(处理环的过程,在stk2 中删除构成环的节点)

如果Root栈的顶元素==v,那么输出相应的强连通分量

至此完结三种求强连通分量的方法,不容易,写的很艰辛啊[哭 !!]

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值