(转)全网最!详!细!tarjan算法讲解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
(转)全⽹最!详!细!tarjan算法讲解
全⽹最详细tarjan算法讲解,我不敢说别的。
反正其他tarjan算法讲解,我看了半天才看懂。
我写的这个,读完⼀遍,发现原来tarjan这么简单!
tarjan算法,⼀个关于图的联通性的神奇算法。
基于DFS(迪法师)算法,深度优先搜索⼀张有向图。
!注意!是有向图。
根据树,堆栈,打标记等种种神(che)奇(dan)⽅法来完成剖析⼀个图的⼯作。
⽽图的联通性,就是任督⼆脉通不通。
的问题。
了解tarjan算法之前你需要知道:
强连通,强连通图,强连通分量,解答树(解答树只是⼀种形式。
了解即可)
不知道怎么办
神奇海螺~:嘟噜噜~!
强连通(strongly connected):在⼀个有向图G⾥,设两个点 a b 发现,由a有⼀条路可以⾛到b,由b⼜有⼀条路可以⾛到a,我们就叫这两个顶点(a,b)强连通。
强连通图:如果在⼀个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。
强连通分量strongly connected components):在⼀个有向图G中,有⼀个⼦图,这个⼦图每2个点都满⾜强连通,我们就叫这个⼦图叫做强连通分量[分量::把⼀个向量分解成⼏个⽅向的向量的和,那些⽅向上的向量就叫做该向量(未分解前的向量)的分量]
举个简单的栗⼦:
⽐如说这个图,在这个图中呢,点1与点2互相都有路径到达对⽅,所以它们强连通.
⽽在这个有向图中,点1 2 3组成的这个⼦图,是整个有向图中的强连通分量。
解答树:就是⼀个可以来表达出递归枚举的⽅式的树(图),其实也可以说是递归图。
反正都是⼀个作⽤,⼀个展⽰从“什么都没有做”开始到“所有结求出来”逐步完成的过程。
“过程!”
神奇海螺结束
tarjan算法,之所以⽤DFS就是因为它将每⼀个强连通分量作为搜索树上的⼀个⼦树。
⽽这个图,就是⼀个完整的搜索树。
为了使这颗搜索树在遇到强连通分量的节点的时候能顺利进⾏。
每个点都有两个参数。
1,DFN[]作为这个点搜索的次序编号(时间戳),简单来说就是第⼏个被搜索到的。
%每个点的时间戳都不⼀样%。
2,LOW[]作为每个点在这颗树中的,最⼩的⼦树的根,每次保证最⼩,like它的⽗亲结点的时间戳这种感觉。
如果它⾃⼰的LOW[]最⼩,那这个点就应该从新分配,变成这个强连通分量⼦树的根节点。
ps:每次找到⼀个新点,这个点LOW[]=DFN[]。
⽽为了存储整个强连通分量,这⾥挑选的容器是,堆栈。
每次⼀个新节点出现,就进站,如果这个点有出度就继续往下找。
直到找到底,每次返回上来都看⼀看⼦节点与这个节点的LOW值,谁⼩就取谁,保证最⼩的⼦树根。
如果找到DFN[]==LOW[]就说明这个节点是这个强连通分量的根节点(毕竟这个LOW[]值是这个强连通分量⾥最⼩的。
)最后找到强连通分量的节点后,就将这个栈⾥,⽐此节点后进来的节点全部出栈,它们就组成⼀个全新的强连通分量。
先来⼀段伪代码压压惊:
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) // 如果节点u还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根
repeat v = S.pop // 将v退栈,为该强连通分量中⼀个顶点
print v
until (u== v)
}
⾸先来⼀张有向图。
⽹上到处都是这个图。
我们就⼀点⼀点来模拟整个算法。
从1进⼊ DFN[1]=LOW[1]=++index ----1
⼊栈 1
由1进⼊2 DFN[2]=LOW[2]=++index ----2
⼊栈 1 2
之后由2进⼊3 DFN[3]=LOW[3]=++index ----3
⼊栈 1 2 3
之后由3进⼊ 6 DFN[6]=LOW[6]=++index ----4
⼊栈 1 2 3 6
之后发现嗯? 6⽆出度,之后判断 DFN[6]==LOW[6]
说明6是个强连通分量的根节点:6及6以后的点出栈。
栈: 1 2 3
之后退回节点3 Low[3] = min(Low[3], Low[6]) LOW[3]还是 3
节点3 也没有再能延伸的边了,判断 DFN[3]==LOW[3]
说明3是个强连通分量的根节点:3及3以后的点出栈。
栈: 1 2
之后退回节点2 嗯?!往下到节点5
DFN[5]=LOW[5]=++index -----5
⼊栈 1 2 5
ps:你会发现在有向图旁边的那个丑的(划掉)搜索树⽤红线剪掉的⼦树,那个就是强连通分量⼦树。
每次找到⼀个。
直接。
⼀剪⼦下去。
半个⼦树就没有了。
结点5 往下找,发现节点6 DFN[6]有值,被访问过。
就不管它。
继续 5往下找,找到了节点1 他爸爸的爸爸。
DFN[1]被访问过并且还在栈中,说明1还在这个强连通分量中,值得发现。
Low[5] = min(Low[5], DFN[1])
确定关系,在这棵强连通分量树中,5节点要⽐1节点出现的晚。
所以5是1的⼦节点。
so
LOW[5]= 1
由5继续回到2 Low[2] = min(Low[2], Low[5])
LOW[2]=1;
由2继续回到1 判断 Low[1] = min(Low[1], Low[2])
LOW[1]还是 1
1还有边没有⾛过。
发现节点4,访问节点4
DFN[4]=LOW[4]=++index ----6
⼊栈 1 2 5 4
由节点4,⾛到5,发现5被访问过了,5还在栈⾥,
Low[4] = min(Low[4], DFN[5]) LOW[4]=5
说明4是5的⼀个⼦节点。
由4回到1.
回到1,判断 Low[1] = min(Low[1], Low[4])
LOW[1]还是 1 。
判断 LOW[1]== DFN[1]
诶?!相等了说明以1为根节点的强连通分量已经找完了。
将栈中1以及1之后进栈的所有点,都出栈。
栈:(⿁都没有了)
这个时候就完了吗?!
你以为就完了吗?!
然⽽并没有完,万⼀你只⾛了⼀遍tarjan整个图没有找完怎么办呢?!所以。
tarjan的调⽤最好在循环⾥解决。
like 如果这个点没有被访问过,那么就从这个点开始tarjan⼀遍。
因为这样好让每个点都被访问到。
来⼀道裸代码。
输⼊:
⼀个图有向图。
输出:
它每个强连通分量。
这个图就是刚才讲的那个图。
⼀模⼀样。
input:
6 8
1 3
1 2
2 4
3 4
3 5
4 6
4 1
5 6
output:
6
5
3 4 2 1
代码
1 #include<cstdio>
2 #include<algorithm>
3 #include<string.h>
4using namespace std;
5struct node {
6int v,next;
7 }edge[1001];
8int DFN[1001],LOW[1001];
9int stack[1001],heads[1001],visit[1001],cnt,tot,index;
10void add(int x,int y)
11 {
12 edge[++cnt].next=heads[x];
13 edge[cnt].v = y;
14 heads[x]=cnt;
15return ;
16 }
17void tarjan(int x)//代表第⼏个点在处理。
递归的是点。
18 {
19 DFN[x]=LOW[x]=++tot;// 新进点的初始化。
20 stack[++index]=x;//进站
21 visit[x]=1;//表⽰在栈⾥
22for(int i=heads[x];i!=-1;i=edge[i].next)
23 {
24if(!DFN[edge[i].v]) {//如果没访问过
25 tarjan(edge[i].v);//往下进⾏延伸,开始递归
26 LOW[x]=min(LOW[x],LOW[edge[i].v]);//递归出来,⽐较谁是谁的⼉⼦/⽗亲,就是树的对应关系,涉及到强连通分量⼦树最⼩根的事情。
27 }
28else if(visit[edge[i].v ]){ //如果访问过,并且还在栈⾥。
29 LOW[x]=min(LOW[x],DFN[edge[i].v]);//⽐较谁是谁的⼉⼦/⽗亲。
就是链接对应关系
30 }
31 }
32if(LOW[x]==DFN[x]) //发现是整个强连通分量⼦树⾥的最⼩根。
33 {
34do{
35 printf("%d ",stack[index]);
36 visit[stack[index]]=0;
37 index--;
38 }while(x!=stack[index+1]);//出栈,并且输出。
39 printf("\n");
40 }
41return ;
42 }
43int main()
44 {
45 memset(heads,-1,sizeof(heads));
46int n,m;
47 scanf("%d%d",&n,&m);
48int x,y;
49for(int i=1;i<=m;i++)
50 {
51 scanf("%d%d",&x,&y);
52 add(x,y);
53 }
54for(int i=1;i<=n;i++)
55if(!DFN[i]) tarjan(i);//当这个点没有访问过,就从此点开始。
防⽌图没⾛完
56return0;
57 }。