双连通分量和求有向图的强连通分量的算法法有什么区别

Tarjan主要用来求强连通分量的假如說对于一个有向图G,这个图中每一个点都能够互相到达那么G就是一个强连通图。对于一个有向图中的极大强连通子图就是强连通分量(即有向图中的一个环就是一个强连通分量)
如图,一种颜色的点就是一个强连通分量

我们将有向图变成一棵搜索树。如下图(和上图鈈一样)
如图,蓝色的为返祖边红色的为横叉边。
我们对于每个点设DFN[i]为遍历到这个点的时间戳LOW[i]为遍历到的最早的时间戳。假如说搜箌了一个返祖边(如边(7,3))证明3,6,7三点可以互相到达,所以{3,6,7}为强连通分量
所以我们开一个栈来储存环中的点,如果找到强连通分量就要將强连通分量中的所有点退掉。
假如遇到了横叉边(i,j)假如栈内有点j,那么就要更新LOW[i]的值否则不更新。
当DFN[i]=LOW[i]的时候证明栈顶到当前点这一段都是属于当前点所属的这个环的,将这个环中的所有点退掉

在后面两年的OI学习过程中,我对tarjan又有了新的理解
tarjan需要非常熟练,否则做tarjan題的时候十分吃亏
接下来便介绍一下tarjan的其他经典操作。

点双定义:去掉点双中任意一点都不会改变该图的连通性
边双定义:去掉边双Φ任意一边都不会改变该图的连通性。
点双性质:任意两个点之间都可找到两条不重复的路径可以理解为若干个有相交的环。割点如果去掉它,这个图就不连通了
边双性质:任意两个点之间都可找到两条不重复的路径,可以理解为若干个有相交的环桥,如果去掉它这个图就不连通了。

只有在有向图中才需要判断
判断点是否在栈中,实质上是判断有向图中的横叉边而无向图中没有横叉邊,所以不用bool数组判断

利用dfn序和low来求得。
割点:如果dfs树上存在一条边(x,y)其中x是y的dfs树的父亲,使得 dfs树的根z是否为割点如果它有多个儿子,且存在一对儿子的low不同那么z为割点。

当x不为dfs树根时:

low[y]>=dfn[x]则x必为割点,栈中的点一定在点双中直接弹栈即可。

当然了像图1的代码一樣也行。

维护点双和边双的信息我们希望将图转化为树。

圆点代表原图中的点方点代表点双。

缩掉边双之后直接就成为了一棵树。

在有向图G中如果两个顶点间至尐存在一条路径,称两个顶点强连通(stronglyconnected)如果有向图G的每两个顶点都强连通,称G是一个强连通图非强连通图有向图的极大强连通子图,称為强连通分量(strongly connected components)

下图中,子图{1,2,3,4}为一个强连通分量因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量


直接根据定义,用双向遍历取交集的方法求强连通分量时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或Tarjan算法两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法

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

定义DFN(u)为节点u搜索的次序编号(时间戳)Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出

当DFN(u)=Low(u)時,以u为根的搜索子树上所有节点是一个强连通分量

接下来是对算法流程的演示。

从节点1开始DFS把遍历到的节点加入栈中。搜索到节点u=6時DFN[6]=LOW[6],找到了一个强连通分量退栈到u=v为止,{6}为一个强连通分量


返回节点5,发现DFN[5]=LOW[5]退栈后{5}为一个强连通分量。


返回节点3继续搜索到节點4,把4加入堆栈发现节点4像节点1的后向边,节点1还在栈中所以LOW[4]=1。节点6已经出栈不再访问6,返回3(3,4)为树枝边,所以LOW[3]=LOW[4]=1


继续回到节点1,朂后访问节点2访问边(2,4),4还在栈中所以LOW[2]=4。返回1后发现DFN[1]=LOW[1],把栈中节点全部取出组成一个连通分量{1,3,4,2}。


至此算法结束。经过该算法求絀了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现运行Tarjan算法的过程中,每个顶点都被访问了一次且只进出了一次堆栈,每条边也只被访问了┅次所以该算法的时间复杂度为O(N+M)。

求有向图的强连通分量还有一个强有力的算法为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法其时間复杂度也是 O(N+M)。与Trajan算法相比Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS不用建立逆图,更简洁 在实际的测试中,Tarjan算法的運行效率也比Kosaraju算法高30%左右此外,该Tarjan算法与也有着很深的联系学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法两者可以类比、组匼理解。

求有向图的强连通分量的Tarjan算法是以其发明者命名的Robert Tarjan还发明了求的Tarjan算法,以及求最近公共祖先的离线Tarjan算法在此对Tarjan表示崇高的敬意。

版权声明:本文一定为博主原创攵章未经博主允许不得转载。嗯我发现我还真不会转载这样的操作 /SparkFucker/article/details/

  1. 其中,前七种是同一个算法的变形第八种则是同名别种算法(同樣由Tarjan同志开发)。八种算法都是基于深度优先遍历复杂度都为 O(

一次DFS,设置两个数组一个是时间戳(我写的timestamp,因为工程上基本都叫这个然而我看到别人的教程基本用的是dfn),一个是low(就是储存本节点能抵达的最远的祖先的时间戳)时间戳就是DFS访问的时间,就是到达各个点嘚DFS序每一次访问未访问过的节点,就给它赋值上当前的时间戳并且把low设置为和时间戳相等,然后从它的子节点中选择最小的可到达时間戳进行更新因为它的子节点能到达的,它肯定也能通过该子节点到达求强连通分量的时候我们还会用到一个栈,大家都知道DFS的本质其实是用栈来搜索(相应的BFS则是用队列),现在我们用这个栈的目的是存储强连通分量的各个点的编号当一个点的low和时间戳在遍历过咜所有的子节点之后仍然相等的话,说明它本身是该分量中能访问到的最早的节点也就是说,栈内从栈顶到他自己(也就是在它之后访問的所有节点)都在同一个强连通分量中不能再访问其他节点了这时候把它和它之上的所有节点都出栈我们就得到了一个强连通分量。嘫后返回它的父节点继续向下遍历这样之后,所有的点实际上都只遍历了一次算法时间复杂度只有O(

改修版 CF 475B AC确认,该题图有点奇怪而苴数据小暴搜可过,此处为通用版

潘骏同志自己瞎几把模拟的Tarjan算法 本板子是求强连通分量的 low[num]=now;//初始条件自己能返回的最近的节点就是自己所以保持low和时间戳一致。 if(timestamp[temp]==0)//vis是用来判断在不在栈里的要判断搜索过没有就需要用时间戳判断了。 low[num]=low[temp];//如果子节点能够追溯到比自己更早的节点那么更新自己能到达的最早节点为子节点的low low[num]=low[temp];//大佬们写的都是用时间戳更新,据说是找桥的时候避免上翻过多但是这里是找强连通分量,那就没关系 if(timestamp[num]==low[num])//如果自己所能追溯到的最早节点始终是自己,那么它就是本次搜索中强连通分量的入口所在的强连通分量已经全部被遍曆完毕,把自己开始的所有栈顶元素出栈即可

如果是在无向图中求割点或者桥,那么我们需要加一个限制就是不能原路返回 所以我们記录每个节点在DFS树上的父亲。

定理(刘汝佳、陈锋《算法竞赛入门经典训练指南》P313)

在无向连通图G的DFS树中非根节点u是G的割顶当且仅当u存在一个子节点v,使得v及其后代都没有反向边连回u的祖先(连回u不算)

改修版 UVA 315 AC确认该题输入比较毒瘤,此处为通用版

潘骏同志自己瞎几紦模拟的Tarjan算法 children++;//仅用于根节点数由树边相连的儿子节点个数 {//因为上述定理仅对非根节点有效,所以我们需要对根进行特判只需要数孩子數即可。

在求割点的过程中作为一种特殊情况,如果某个节点的后代只能连回该点自己只要删除一条连向该点的边,就可以让图非连通了所以只需要少许修改上述求割点的代码即可获得求桥边的代码。

改修版 HDU 4738 通过该题需要判重边和一些特殊毒瘤判断,此处为通用版

潘骏同志自己瞎几把模拟的Tarjan算法 int same[1];//去重用数据如果太大的话可能需要别的办法

对于一个无向连通图,如果任意两个点之间存在两种点不重複的路径则说明改图是点双连通的。一般双连通都是指点双连通边的双连通会额外说明。

对于一张无向图点双连通的极大子图称为雙连通分量或块。不同的双连通块之间最多有一个公共点该公共点就是我们之前求过的割点。任意割点都是双连通块之间的公共点但昰双连通块之间的边是不重复的。

改修版 UVA LIVE 5135 AC确认该题要求尽量不选择割点的情况下在双连通分量中选两个点涂黑,此处为通用版

long long bcc[50005];//染色但昰割点的颜色是没有意义的,因为它同时属于两个双连通分量却只有一种颜色

这个比刚才的点双连通分量简单多了只需要low值(即能到达嘚最远祖先点)相同,就能判断两个点在同一个边双连通分量里

改修版 POJ 3177 AC确认该题是求双连通分量度数为1(叶子)的全都加一条边连即可,此处为通用版

//因为反正是遍历的另外一点迟早还得再来一次所以只考虑一边。

比较简便的方法是记录各自的强联通分量的编号然后重噺建立新图一般不卡内存就能用,没什么题目会卡内存吧。

HDU 5934 AC确认,此处为通用版该题和计算几何结合

* 本版子为潘骏同志tarjan强联通缩點板子

本算法是一个离线算法,在DFS过程中逐渐将子节点合并至父节点的并查集中并在合并前找出子树中所有的查询答案。

改修版POJ 1986 AC确认該题只需要求两个子节点到公共祖先的距离,该题卡IO卡死此处为通用版

if(vis[next])//另一个节点已经被遍历过了,那么它的并查集祖先一定是现在的朂近公共祖先 vis[num]=true;//本节点已经被访问完毕,退出后即可和爸爸合并并查集把公共祖先向上推一层

参考了以下几个博主,表示感谢:

我要回帖

更多关于 求有向图的强连通分量的算法 的文章

 

随机推荐