转 tarjan算法求强连通分量

无意中想起图的强连通分量来,之前也一直想写所以今天决定来填这个坑。PS:由于本人比较懒,之前做过一个讲解的PPT,不过那是好遥远之前,年代已久早已失传,所以本文里的图来自网络。以后周末都用来填坑也挺好。

------------------------------分割线-----------------------------------------

在有向图G中,如果两个顶点间至少存在一条路径,那么这两个顶点就是强连通(strongly connected)。

如果有向图G的每两个顶点都强连通,称G是一个强连通图。

非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

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

求强连通分量的方法有多种,比如你可以用双向遍历取交集,不过复杂度稍高一点O(N^2+M)。我们现在来看一种复杂度为O(N+M)的算法:Tarjan算法。代码如下:

 1 int index,bcnt;
 2 int DFN[100],LOW[100],stack[100],belong[100];
 3 bool instack[100];
 4
 5 void tarjan(int i)
 6 {
 7 int j;
 8 DFN[i]=LOW[i]=++index;
 9 instack[i]=true;
10 stack[++top]=i;
11 for (edge *e=V[i];e;e=e->next)
12 {
13 j=e->t;
14 if (!DFN[j])// 如果节点j未被访问过
15 {
16 tarjan(j);
17 if (LOW[j]<LOW[i])
18 LOW[i]=LOW[j];
19 }
20 else if (instack[j] && DFN[j]<LOW[i])// 如果节点j还在栈内
21 LOW[i]=DFN[j];
22 }
23 if (DFN[i]==LOW[i])// 如果节点i是强连通分量的根
24 {
25 bcnt++;
26 do
27 {
28 j=stack[top--];
29 instack[j]=false;
30 belong[j]=bcnt;
31 }
32 while (j!=i);
33 }
34 }
35 void solve()
36 {
37 int i;
38 top=bcnt=index=0;
39 memset(DFN,0,sizeof(DFN));
40 for (i=1;i<=N;i++)
41 if (!DFN[i])
42 tarjan(i);
43 }

Tarjan算法是基于对图的深度优先遍历,把每个强连通分量当做搜索树中的一棵子树。搜索时,把未处理的节点放到堆栈中,并借助时间戳在回溯时判断栈顶到栈中的节点是否为一个强连通分量。

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

算法执行过程如下(PS:再次强调图片来自网络,其中栈的画的不是很好,自顶向下弹栈会更直观一点,结果图弄反了):

从节点1开始进行深度优先遍历,把每个节点加入栈中并打上时间戳,也就是该节点是第几个被访问。当我们走到节点u=6时发现DFN[6]=LOW[6],那么便开始弹栈直到u=v为止,{6}为一个强连通分量。

同理,返回节点5时也发现DFN[5]=LOW[5],继续弹栈,{5}为一个强连通分量。

返回节点3,继续深度优先遍历到节点4,把4加入堆栈。遍历节点4的邻居节点发现虽然节点6已经出栈,但是节点1这小子还在栈中,节点4向节点1有后向边,所以LOW[4]=1。继续返回节点3,LOW[3]=LOW[4]=1。

继续回到节点1,遍历到节点2。遍历2的邻居节点,发现4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,得到DFN[1]=LOW[1],所以把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

至此,算法结束。得到图中的三个强连通分量{1,3,4,2},{5},{6}。

以上便是Tarjan算法的执行过程。下面我们说一下图的割点、桥与双连通分支的问题。

在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。一个图的点连通度的定义为,最小割点集合中的顶点数。类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。一个图的边连通度的定义为,最小割边集合中的边数。

如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通或重连通。一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)。

如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。

在图G的所有子图G‘中,如果G‘是双连通的,则称G‘为双连通子图。如果一个双连通子图G‘它不是任何一个双连通子图的真子集,则G‘为极大双连通子图。双连通分支(biconnected component),或重连通分支,就是图的极大双连通子图。特殊的,点双连通分支又叫做块。

求割点与桥:

通过上面讲的Tarjan算法有:

Low(u)=Min { DFS(u) DFS(v)}  此时(u,v)为后向边(返祖边) 即等价于 DFS(v)<DFS(u)且v不为u的父亲节点

OR

Low(u)=Min{DFS(U) Low(v)}   此时(u,v)为树枝边(父子边)

一个顶点u是割点,则要么 u为树根,且u有多于一个子树。或者是 u不为树根,且满足存在(u,v)为父子边即u为v在搜索树中的父亲,使得DFS(u)<=Low(v)。

一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。

求双连通分支:

对于求点双连通分支,建立一个栈,存储当前双连通分支,在遍历时,每找到一条树枝边或后向边,就把这条边加入栈中。如果遇到满足DFS(u)<=Low(v)时,说明u是一个割点,此时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。

对于边双连通分支,只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。

时间: 2024-08-07 01:32:45

转 tarjan算法求强连通分量的相关文章

图论算法(6)(更新版) --- Tarjan算法求强连通分量

之前Tarjan算法求强连通分量博文中,代码实现用到了固定大小数组,扩展起来似乎并不是很方便,在java里这样来实现本身就是不太妥当的,所以下面给出一个更新版本的代码实现: package test; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util

HDU 1269 迷宫城堡 tarjan算法求强连通分量

基础模板题,应用tarjan算法求有向图的强连通分量,tarjan在此处的实现方法为:使用栈储存已经访问过的点,当访问的点离开dfs的时候,判断这个点的low值是否等于它的出生日期dfn值,如果相等,那这个点就在一个强连通分量里面,此时从栈中向外取出元素,知道取出的元素与这个点的值相等时结束,我们所有取出的点与这个点在同一个强连通分量里.下面是代码,其实代码里本来不需要id数组记录点属于哪个强连通分量的,因为题目没有做要求,但是为了保留模板完整还是带着了,以供以后复习使用. #include<c

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

[时光蒸汽喵带你做专题]最近公共祖先 LCA (Lowest Common Ancestors)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili tarjan LCA - YouTube Tarjan算法_LCA - A_Bo的博客 - CSDN博客 Tarjan离线算法求最近公共祖先(LCA) - 初学者 - CSDN博客 最近公共祖先(LCA) - riteme.site Fuzhou University OnlineJudge P3379 [模板]最近公共祖先(LCA) - 洛谷 |

图论算法(6) --- Tarjan算法求强连通分量

注:此算法以有向图作为输入,并按照所在的强连通分量给出其顶点集的一个划分.graph中的每个节点只在一个强连通分量里出现,即使是单点. 任选一点开始进行深度优先搜索(若dfs结束后仍有未访问的节点,则再从中任选一点再从进行).搜索过程中已访问的节点不再访问.搜索树的若干子树构成了图的强连通分量. 节点按照被访问的顺序存入栈中.从搜索树的子树返回至一个节点时,检查该节点是否是某一连通分量的根节点,并将其从栈中删除.如果某节点是强连通分量的根,则在它之前出栈且还不属于其他强连通分量的节点构成了该节点

Tarjan算法求强连通分量

一.操作过程:tarjan算法的基础是DFS.我们准备两个数组Low和Dfn.Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的 Dfn值(很绕嘴,往下看你就会明白),Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的.根据以下几条规则,经过搜索遍历该图(无需回溯)和 对栈的操作,我们就可以得到该有向图的强连通分量. 1.数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间. 2.堆栈:每搜索到一个点,将它压入栈顶. 3.当点p有与点p’相连时,如

【算法】Tarjan算法求强连通分量

概念: 在有向图G中,如果两个定点u可以到达v,并且v也可以到达u,那么我们称这两个定点强连通. 如果有向图G的任意两个顶点都是强连通的,那么我们称G是一个强连通图. 一个有向图中的最大强连通子图,称为强连通分量. tarjan的主要思想: 从一个点开始DFS,记录两个数组,dfn[]和low[]. 其中,dfn[i]指的是到达第i个点的时间. low[i]指第i个点直接或间接可到达的点中的最小dfn[j]. low[i]数组的初始值为dfn[i]. 举个例子,如图所示: 假如我们从第一个点开始

Tarjan算法分解强连通分量(附详细参考文章)

Tarjan算法分解强连通分量 算法思路: 算法通过dfs遍历整个连通分量,并在遍历过程中给每个点打上两个记号:一个是时间戳,即首次访问到节点i的时刻,另一个是节点u的某一个祖先被访问的最早时刻. 时间戳用DFN数组存储,最早祖先用low数组来存,每次dfs遍历到一个节点u,即让这两个记号等于当前时刻,在后面回溯或者判断的过程中在来更新low,DNF是一定的,因为第一次访问时刻一定.然后遍历u的子节点,也就是跟u相连的点v,依次看子节点的时间戳有没有打上,也就是看他有没有被访问过.\(1\).没

tarjan算法(强连通分量 + 强连通分量缩点 + 桥 + 割点 + LCA)

这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量,缩点,桥,割点,LCA等,日后写到相应的模板题我就会放上来. 1.强连通分量(分量中是任意两点间都可以互相到达) 按照深度优先遍历的方式遍历这张图. 遍历当前节点所出的所有边.在遍历过程中: ( 1 ) 如果当前边的终点还没有访问过,访问. 回溯回来之后比较当前节点的low值和终点的low值.将较小

Tarjan算法【强连通分量】

转自:byvoid:有向图强连通分量的Tarjan算法 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的所有节点是否为一个强连通分量. 有两个概念:1.时间戳,2.追溯值 时间戳是dfs遍历节点的次序. 定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的栈中节点最小的次序号.由定义可以得出: 1 Low(u)=min{ 2 DFN(u), // 自己的