0
点赞
收藏
分享

微信扫一扫

tarjan算法的学习 uva12167,uva315,uva796


图的强连通&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】:



    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. //返图  
    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. //后序  
    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. //连通分支+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. "%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、强连通分量(有向图)

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

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

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

    【代码 uva12167】:

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



    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. //到达此点的时间  
    26. //压入栈  
    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  
    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. sizeof(Scc));  
    55. 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. sizeof(head));  
    70.         cnt=0;  
    71. while(m--)  
    72.         {  
    73. "%d%d",&u,&v);  
    74.             add(u,v);  
    75.         }  
    76. int sig=tarjan(n);  
    77. if(sig==1)puts("0");  
    78. else  
    79.         {  
    80. sizeof(in));  
    81. 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】:


    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. //到达此点的时间  
    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. //此处是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. sizeof(iscut));  
    48. 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. sizeof(head));  
    63.         cnt=0;  
    64. while(scanf("%d",&u),u)  
    65.         {  
    66. do{  
    67. "%d",&v);  
    68.                 add(u,v);  
    69.                 add(v,u);  
    70. while(getchar()!='\n');  
    71.         }  
    72.         cout<<tarjan(n)<<endl;  
    73.     }  
    74. }

    例题,uva796 求桥数并输出

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

    【代码uva796】



    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. //到达此点的时间  
    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. //标记桥  
    34.         }  
    35. else //祖先  
    36. //此处是dfn!不是low,因为low可能已被更新,就跨过了割点  
    37.     }  
    38. }  
    39. int tarjan(int n)  
    40. {  
    41. int bridge=0;//桥数  
    42. int lay=1;//时间戳  
    43. sizeof(bri));  
    44. 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. sizeof(head));  
    64.         cnt=0;  
    65. for(int i=1;i<=n;i++)  
    66.         {  
    67. "%d (%d)",&u,&q);u++;  
    68. while(q--)  
    69.             {  
    70. "%d",&v);v++;  
    71.                 add(u,v);  
    72.                 add(v,u);  
    73.             }  
    74.         }  
    75. "%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. "%d - %d\n",ans[i].s-1,ans[i].t-1);  
    89. "");  
    90.     }  
    91. }  
    92.

    举报

    相关推荐

    0 条评论