tarjan算法的学习 uva12167,uva315,uva796

来源:互联网 发布:中国经济实力 知乎 编辑:程序博客网 时间:2024/06/15 04:06

图的强连通&tarjan算法

强连通图:如果有向图G中的任意两个点u,v是互相可到达的,则称图G为强连通图。否则G为非强连通图。

强连通分量:若有向图G为非强连通图,它的子图G' 是强连通图,则称G' 为G的强连通分量。(极大强连通子图)


返图:将有向图G中的所有边的方向逆置,即u->v变为v->u


定理:对于一个有向图G,按照dfs的后序遍历到的点,对图G的返图进行一次dfs,就能得到其中一个强联通分量。首先,对原图dfs能连通这些点,然后对返图也能dfs到的点,是一个强连通分量。


求强连通分量的作用 :

 把有向图中具有相同性质的点找出来,形成一个集合(缩点),建立缩图,能够方便地进行其它操作,而且时间效率会大大地提高,原先对多个点的操作可以简化为对它们所属的缩点的操作。  求强连通分量常常用于求拓扑排序之前,因为原图往往有环,无法进行拓扑排序,而求强连通分量后所建立的缩图则是有向无环图,方便进行拓扑排序。  缩点操作在这篇中用过(最小树形图):点击打开链接

以下主要内容:

kosaraju算法求强连通分量

tarjan算法求强连通分量、割点、桥

一、kosaraju算法。

1、基本思路

先对原图进行一次深搜,记录下访问的后序遍历顺序,即每个点在dfs的离开时间,可用栈存储。然后按照离开时间进行第二次深搜,搜返图,在第一次深搜中能到达的点,若在返图的深搜中也能到达,说明这些点是一个强联通分量。

【代码 hdu1269】:

[cpp] view plain copy
  1. <span style="font-size:18px;">#include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <iostream>  
  5. #include <algorithm>  
  6. #define mset(a,i) memset(a,i,sizeof(a))  
  7. using namespace std;  
  8. const int MAX=1e5+5;  
  9. struct node{  
  10.     int s,t,next;  
  11. }e[MAX<<3];  
  12. int head[MAX],head2[MAX],cnt;//存原图,返图  
  13. bool vis[MAX];//标记访问的节点  
  14. int sta[MAX],top;//记录dfs后序  
  15. int Scc[MAX];//可以记录每个节点的分量归属  
  16. void add(int u,int v)//建图  
  17. {  
  18.     e[cnt]=node{u,v,head[u]};  
  19.     head[u]=cnt++;  
  20.     e[cnt]=node{v,u,head[v]};  
  21.     head2[v]=cnt++;//返图  
  22. }  
  23. void init()  
  24. {  
  25.     mset(head,-1);  
  26.     mset(head2,-1);  
  27.     cnt=0;  
  28. }  
  29. void dfs1(int u)  
  30. {  
  31.     vis[u]=1;  
  32.     for(int i=head[u];~i;i=e[i].next)  
  33.     {  
  34.         int t=e[i].t;  
  35.         if(!vis[t])dfs1(t);  
  36.     }  
  37.     sta[top++]=u;//后序  
  38. }  
  39. void dfs2(int u,int sig)  
  40. {  
  41.     vis[u]=1;  
  42.     Scc[u]=sig;  
  43.     for(int i=head2[u];~i;i=e[i].next)  
  44.     {  
  45.         int t=e[i].t;  
  46.         if(!vis[t])dfs2(t,sig);  
  47.     }  
  48. }  
  49. int kosaraju(int n)  
  50. {  
  51.     top=0;  
  52.     mset(vis,0);  
  53.     for(int i=1;i<=n;i++)  
  54.     {  
  55.         if(!vis[i])dfs1(i);  
  56.     }//第一次dfs跑原图记离开时间  
  57.     int sig=0;  
  58.     mset(vis,0);  
  59.     for(int i=top-1;i>=0;i--)  
  60.     {  
  61.         if(!vis[sta[i]])  
  62.         {  
  63.             dfs2(sta[i],++sig);//连通分支+1  
  64.         }  
  65.     }//第2次dfs跑返图求连通分量数  
  66.     return sig;  
  67. }  
  68. int main()  
  69. {  
  70.     int n,m,u,v;  
  71.     while(cin>>n>>m,n)  
  72.     {  
  73.         init();  
  74.         while(m--)  
  75.         {  
  76.             scanf("%d%d",&u,&v);  
  77.             add(u,v);  
  78.         }  
  79.         int ans=kosaraju(n);  
  80.         if(ans==1)puts("Yes");  
  81.         else puts("No");  
  82.     }  
  83.     return 0;  
  84. }</span>  
二、tarjan算法。

1、强连通分量(有向图)

详解:http://blog.csdn.net/justlovetao/article/details/6673602

增加两个数组,dfn[]表是dfs到达每个节点的时间。low[]记录当前点或其子树中的点能到达的拥有最小时间戳的点

若一个点dfs过程中碰到了已访问过的点,说明形成环,dfs回溯回去,回到这个环中时间戳最小的点,

一定有dfn[ u ] == low[ u ],从该点开始将栈中它的后续点依次出栈,即为一个连通分量

【代码 uva12167】:

给定有向图,问需要加几条边可以强连通?先用tarjan算强连通数,然后用缩点法计算连通块之间的入度、出度,缺多少。取max(in, out)

[cpp] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<iostream>  
  5. #include<algorithm>  
  6. #include<map>  
  7. using namespace std;  
  8. const int MAX=202020;  
  9. struct node{  
  10.     int s,t,next;  
  11. }e[MAX];  
  12. int head[MAX],cnt;  
  13. void add(int u,int v)  
  14. {  
  15.     e[cnt]=node{u,v,head[u]};  
  16.     head[u]=cnt++;  
  17. }  
  18. int dfn[MAX];//每个节点的访问时间编号  
  19. int low[MAX];//每个点能到达的最小编号  
  20. int sta[MAX],top;  
  21. int Scc[MAX];//每个点所属的分量 序号  
  22. int in[MAX],out[MAX];  
  23. void tardfs(int u,int &lay,int &sig)  
  24. {  
  25.     low[u]=dfn[u]=lay++;//到达此点的时间  
  26.     sta[top++]=u;//压入栈  
  27.     for(int i=head[u];~i;i=e[i].next)  
  28.     {  
  29.         int t=e[i].t;  
  30.         if(dfn[t]==0)  
  31.         {  
  32.             tardfs(t,lay,sig);  
  33.             low[u]=min(low[u],low[t]);  
  34.         }  
  35.         else if(!Scc[t])//访问过了  
  36.             low[u]=min(low[u],dfn[t]);//强连通求法可以用low  
  37.     }  
  38.     if(low[u]==dfn[u])//u不能到达任何之前走过的点  
  39.     {  
  40.         sig++;  
  41.         while(1)  
  42.         {  
  43.             int j=sta[--top];//出栈  
  44.             Scc[j]=sig;  
  45.             if(j==u)break;  
  46.         }//包含u的连通分量出栈  
  47.     }  
  48. }  
  49. int tarjan(int n)  
  50. {  
  51.     int sig=0;//强连通数  
  52.     int lay=1;//时间戳  
  53.     top=0;  
  54.     memset(Scc,0,sizeof(Scc));  
  55.     memset(dfn,0,sizeof(dfn));//时间戳归0  
  56.     for(int i=1;i<=n;i++)  
  57.     {  
  58.         if(!dfn[i])tardfs(i,lay,sig);  
  59.     }  
  60.     return sig;//返回连通数  
  61. }  
  62. int main()  
  63. {  
  64.     int n,m,u,v,T;  
  65.     cin>>T;  
  66.     while(T--)  
  67.     {  
  68.         cin>>n>>m;  
  69.         memset(head,-1,sizeof(head));  
  70.         cnt=0;  
  71.         while(m--)  
  72.         {  
  73.             scanf("%d%d",&u,&v);  
  74.             add(u,v);  
  75.         }  
  76.         int sig=tarjan(n);  
  77.         if(sig==1)puts("0");  
  78.         else  
  79.         {  
  80.             memset(in,0,sizeof(in));  
  81.             memset(out,0,sizeof(out));  
  82.             for(int i=1;i<=n;i++)//缩点统计度  
  83.             {  
  84.                 for(int j=head[i];~j;j=e[j].next)  
  85.                     if(Scc[i]!=Scc[e[j].t])  
  86.                         in[Scc[e[j].t]]=out[Scc[i]]=1;  
  87.             }  
  88.             int a=0,b=0;  
  89.             for(int i=1;i<=sig;i++)  
  90.             {  
  91.                 if(in[i]==0)a++;  
  92.                 if(out[i]==0)b++;//没出度,需要加边  
  93.             }  
  94.             cout<<max(a,b)<<endl;  
  95.         }  
  96.     }  
  97. }  


2、割点与割边(割顶&桥)(无向图)

割顶:若去掉一个点和与这个点相连的边后,图不再连通,则这个点是割顶。

基本思路:dfn数组记录时间戳,去更新low数组代表的可到达的祖先最低时间戳。

若满足low[v] >= dfn[u],说明u的子树中v点不能到达u的祖先,因此u点是割点。

:去掉这条边,图不再连通。

若满足low[v] > dfn[u],说明u的子树中v点不能到达u及其祖先,因此边<u,v>是割边

例题,uva 315:  tarjan算法求割点

割点需要特判根节点是否是割点,方法是统计根节点在dfs时得到的子树数目child,若>1说明root是割点。

【代码uva315】:

[cpp] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<iostream>  
  5. #include<algorithm>  
  6. using namespace std;  
  7. const int MAX=202020;  
  8. struct node{  
  9.     int s,t,next;  
  10. }e[MAX];  
  11. int head[MAX],cnt;  
  12. void add(int u,int v)  
  13. {  
  14.     e[cnt]=node{u,v,head[u]};  
  15.     head[u]=cnt++;  
  16. }  
  17. int dfn[MAX];//每个节点的访问时间编号  
  18. int low[MAX];//每个点能到达的最小编号  
  19. bool iscut[MAX];//标记割点  
  20. void tardfs(int u,int &lay,int fa)  
  21. {  
  22.     low[u]=dfn[u]=lay++;//到达此点的时间  
  23.     int child=0;//子树数目  
  24.     for(int i=head[u];~i;i=e[i].next)  
  25.     {  
  26.         int t=e[i].t;  
  27.         if(t==fa)continue;  
  28.         if(dfn[t]==0)  
  29.         {  
  30.             tardfs(t,lay,u);  
  31.             low[u]=min(low[u],low[t]);  
  32.             child++;  
  33.   
  34.             if(u!=1&&low[t]>=dfn[u])//满足子树回不到祖先  
  35.             iscut[u]=1;  
  36.         }  
  37.         else //祖先  
  38.             low[u]=min(low[u],dfn[t]);//此处是dfn!不是low,因为low可能已被更新,就跨过了割点  
  39.   
  40.     }  
  41.     if(u==1&&child>1)iscut[1]=1;  
  42. }  
  43. int tarjan(int n)  
  44. {  
  45.     int sig=0;//割点数  
  46.     int lay=1;//时间戳  
  47.     memset(iscut,0,sizeof(iscut));  
  48.     memset(dfn,0,sizeof(dfn));//时间戳归0  
  49.     for(int i=1;i<=n;i++)  
  50.     {//若无向图连通,只进入1一次就全部tarjan出来了  
  51.         if(!dfn[i])tardfs(i,lay,-1);  
  52.     }  
  53.     for(int i=1;i<=n;i++)  
  54.         if(iscut[i])sig++;  
  55.     return sig;//返回割点数  
  56. }  
  57. int main()  
  58. {  
  59.     int n,m,q,u,v;  
  60.     while(cin>>n,n)  
  61.     {  
  62.         memset(head,-1,sizeof(head));  
  63.         cnt=0;  
  64.         while(scanf("%d",&u),u)  
  65.         {  
  66.             do{  
  67.                 scanf("%d",&v);  
  68.                 add(u,v);  
  69.                 add(v,u);  
  70.             }while(getchar()!='\n');  
  71.         }  
  72.         cout<<tarjan(n)<<endl;  
  73.     }  
  74. }  
例题,uva796 求桥数并输出

题意是求出桥的数目并输出。

【代码uva796】

[cpp] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<iostream>  
  5. #include<algorithm>  
  6. using namespace std;  
  7. const int MAX=202020;  
  8. struct node{  
  9.     int s,t,next;  
  10. }e[MAX];  
  11. int head[MAX],cnt;  
  12. void add(int u,int v)  
  13. {  
  14.     e[cnt]=node{u,v,head[u]};  
  15.     head[u]=cnt++;  
  16. }  
  17. int dfn[MAX];//每个节点的访问时间编号  
  18. int low[MAX];//每个点能到达的最小编号  
  19. bool bri[MAX];//标记桥  
  20. node ans[MAX];  
  21. void tardfs(int u,int &lay,int fa)  
  22. {  
  23.     low[u]=dfn[u]=lay++;//到达此点的时间  
  24.     for(int i=head[u];~i;i=e[i].next)  
  25.     {  
  26.         int t=e[i].t;  
  27.         if(t==fa)continue;  
  28.         if(dfn[t]==0)  
  29.         {  
  30.             tardfs(t,lay,u);  
  31.             low[u]=min(low[u],low[t]);  
  32.             if(low[t]>dfn[u])//满足子树回不到祖先和自己  
  33.                 bri[i]=1;//标记桥  
  34.         }  
  35.         else //祖先  
  36.             low[u]=min(low[u],dfn[t]);//此处是dfn!不是low,因为low可能已被更新,就跨过了割点  
  37.     }  
  38. }  
  39. int tarjan(int n)  
  40. {  
  41.     int bridge=0;//桥数  
  42.     int lay=1;//时间戳  
  43.     memset(bri,0,sizeof(bri));  
  44.     memset(dfn,0,sizeof(dfn));//时间戳归0  
  45.     for(int i=1;i<=n;i++)  
  46.     {//若无向图连通,只进入1一次就全部tarjan出来了  
  47.         if(!dfn[i])tardfs(i,lay,-1);  
  48.     }  
  49.     for(int i=0;i<cnt;i++)  
  50.         if(bri[i])bridge++;  
  51.     return bridge;//返回桥  
  52. }  
  53. bool cmp(node a,node b)  
  54. {  
  55.     if(a.s==b.s)return a.t<b.t;  
  56.     return a.s<b.s;  
  57. }  
  58. int main()  
  59. {  
  60.     int n,m,q,u,v;  
  61.     while(cin>>n)  
  62.     {  
  63.         memset(head,-1,sizeof(head));  
  64.         cnt=0;  
  65.         for(int i=1;i<=n;i++)  
  66.         {  
  67.             scanf("%d (%d)",&u,&q);u++;  
  68.             while(q--)  
  69.             {  
  70.                 scanf("%d",&v);v++;  
  71.                 add(u,v);  
  72.                 add(v,u);  
  73.             }  
  74.         }  
  75.         printf("%d critical links\n",tarjan(n));//桥数  
  76.         int t=0;  
  77.         for(int i=0;i<cnt;i++)//对标记的边筛选排序并输出  
  78.         {  
  79.             if(bri[i])  
  80.             {  
  81.                 if(e[i].s<e[i].t)  
  82.                     ans[t++]=node{e[i].s,e[i].t};  
  83.                 else ans[t++]=node{e[i].t,e[i].s};  
  84.             }  
  85.         }  
  86.         sort(ans,ans+t,cmp);  
  87.         for(int i=0;i<t;i++)  
  88.             printf("%d - %d\n",ans[i].s-1,ans[i].t-1);  
  89.         puts("");  
  90.     }  
  91. }  

原创粉丝点击