tarjan看了几次了,这次才感觉明白了。

来源:互联网 发布:今天美国钻井平台数据 编辑:程序博客网 时间:2024/05/06 12:53

Tarjan算法的操作原理如下:

Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。

可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。

这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。

强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。

如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。

算法框架

Dfn(u)为节点u搜索的次序编号(时间戳)。

Low(u)我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值,为u或者u的子树能够追溯到的最早的栈中的节点的次序号dfn。

1.数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。

2.堆栈:每搜索到一个点,将它压入栈顶。

3.当点u有与点v相连时,如果此时(时间为dfn[u]时)v不在栈中,u的low值为两点的low值中较小的一个。(这是顺着树边向下找)

4.当点u有与点v相连时,如果此时(时间为dfn[u]时)v在栈中,u的low值为u的low值和v的dfn值中较小的一个。(出现环了,这是一条返祖边,一个联通分量可能有多个环)

5.每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。

6.继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。

伪代码

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)

}

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

#include<cstdio>#include<algorithm>#include<string.h>#include<iostream> using namespace std;struct node {int v,next;} edge[1001];int DFN[1001],LOW[1001];int stack[1001],heads[1001],visit[1001],cnt,tot,index;void add(int x,int y) {edge[++cnt].next=heads[x];edge[cnt].v = y;heads[x]=cnt;return ;}void tarjan(int x) { //代表第几个点在处理。递归的是点。DFN[x]=LOW[x]=++tot;// 新进点的初始化。cout<<x<<":"<<DFN[x]<<" "<<LOW[x]<<endl;stack[++index]=x;//进站visit[x]=1;//表示在栈里for(int i=heads[x]; i!=-1; i=edge[i].next) {if(!DFN[edge[i].v]) {//如果没访问过    cout<<"  in u-v:   "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" low"<<edge[i].v<<"="<<LOW[edge[i].v]<<endl;tarjan(edge[i].v);//往下进行延伸,开始递归cout<<"      out:  "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" ";LOW[x]=min(LOW[x],LOW[edge[i].v]);//递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。cout<<"  low"<<edge[i].v<<"="<<LOW[edge[i].v]<<"=> low"<<x<<":"<<LOW[x]<<endl; } else if(visit[edge[i].v ]) { //出现了环:如果访问过,并且还在栈里。 cout<<"  else"<<edge[i].v<<":instack  "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" ";LOW[x]=min(LOW[x],DFN[edge[i].v]);//比较谁是谁的儿子/父亲。找出所有子树中最早出现的dfn cout<<"  dfn"<<edge[i].v<<"="<<DFN[edge[i].v]<<"=>low"<<x<<"="<<LOW[x]<<endl;}}if(LOW[x]==DFN[x]) { //发现是整个强连通分量子树里的最小根。     cout<<"ssc:";do {printf("   %d dfn=%d, low=%d    \n",stack[index],DFN[stack[index]],LOW[stack[index]]);visit[stack[index]]=0;index--;} while(x!=stack[index+1]);//出栈,并且输出。printf("\n");}return ;}int main() {memset(heads,-1,sizeof(heads));int n,m;scanf("%d%d",&n,&m);int x,y;for(int i=1; i<=m; i++) {scanf("%d%d",&x,&y);add(x,y);}for(int i=1; i<=n; i++){cout<<i<<endl; if(!DFN[i])  tarjan(i);//当这个点没有访问过,就从此点开始。防止图没走完}   return 0;}

数据:及执行结果

6 7

1 2

2 3

3 5

3 4

4 1

5 6

5 4

1

1:1 1

  inu-v:   1->2  low1=1 low2=0

2:2 2

  inu-v:   2->3  low2=2 low3=0

3:3 3

  inu-v:   3->4  low3=3 low4=0

4:4 4

 else1:instack  4->1  low4=4  dfn1=1=>low4=1

     out:  3->4  low3=3  low4=1=> low3:1

  inu-v:   3->5  low3=1 low5=0

5:5 5

 else4:instack  5->4  low5=5  dfn4=4=>low5=4

  inu-v:   5->6  low5=4 low6=0

6:6 6

ssc:  6 dfn[]=6, low[]=6

 

     out:  5->6  low5=4  low6=6=> low5:4

     out:  3->5  low3=1  low5=4=> low3:1

      out: 2->3  low2=2   low3=1=> low2:1

     out:  1->2  low1=1  low2=1=> low1:1

ssc:  5 dfn[]=5, low[]=4

   4dfn[]=4, low[]=1

   3dfn[]=3, low[]=1

   2dfn[]=2, low[]=1

   1dfn[]=1, low[]=1

codevs1332 上白泽慧

Poj2186 Popular Cows


阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 邮编查询大全 云南邮政编码 保山邮政编码 家庭邮政编码是什么 信用卡邮寄查询 邮寄编码查询 查邮政编码 邮政编码怎么查询 邮政编码查询大全 怎么查邮政编码 查询邮政编码 北京市邮政编码查询 如何查询邮政编码 北京的邮政编码 如何查邮政编码 政编码查询 北京邮政编码查询大全 邮编码 邮政编码查询广东 邮政编码大全 福建省邮政编码查 全国地区县乡镇邮政编码查询 我的邮政编码 邮箱编码查询 地区邮政编码查询 邮政编码是啥 查邮政编码怎么查 北京邮政编码查询 河北邮政编码查询 家庭邮政编码查询 邮局编号查询 编码查询 邮缘 吉吉屋邮购 歌诗达邮轮 千万别去邮轮游 邮轮旅游2人花多少钱 世界梦号邮轮 鼓浪屿号邮轮 量子号邮轮 赛琳娜号邮轮