图的强连通&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】:
- <span style="font-size:18px;">#include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <iostream>
- #include <algorithm>
- #define mset(a,i) memset(a,i,sizeof(a))
- using namespace std;
- const int MAX=1e5+5;
- struct node{
- int s,t,next;
- }e[MAX<<3];
- int head[MAX],head2[MAX],cnt;
- bool vis[MAX];
- int sta[MAX],top;
- int Scc[MAX];
- void add(int u,int v)
- {
- e[cnt]=node{u,v,head[u]};
- head[u]=cnt++;
- e[cnt]=node{v,u,head[v]};
- head2[v]=cnt++;
- }
- void init()
- {
- mset(head,-1);
- mset(head2,-1);
- cnt=0;
- }
- void dfs1(int u)
- {
- vis[u]=1;
- for(int i=head[u];~i;i=e[i].next)
- {
- int t=e[i].t;
- if(!vis[t])dfs1(t);
- }
- sta[top++]=u;
- }
- void dfs2(int u,int sig)
- {
- vis[u]=1;
- Scc[u]=sig;
- for(int i=head2[u];~i;i=e[i].next)
- {
- int t=e[i].t;
- if(!vis[t])dfs2(t,sig);
- }
- }
- int kosaraju(int n)
- {
- top=0;
- mset(vis,0);
- for(int i=1;i<=n;i++)
- {
- if(!vis[i])dfs1(i);
- }
- int sig=0;
- mset(vis,0);
- for(int i=top-1;i>=0;i--)
- {
- if(!vis[sta[i]])
- {
- dfs2(sta[i],++sig);
- }
- }
- return sig;
- }
- int main()
- {
- int n,m,u,v;
- while(cin>>n>>m,n)
- {
- init();
- while(m--)
- {
- scanf("%d%d",&u,&v);
- add(u,v);
- }
- int ans=kosaraju(n);
- if(ans==1)puts("Yes");
- else puts("No");
- }
- return 0;
- }</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)
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<iostream>
- #include<algorithm>
- #include<map>
- using namespace std;
- const int MAX=202020;
- struct node{
- int s,t,next;
- }e[MAX];
- int head[MAX],cnt;
- void add(int u,int v)
- {
- e[cnt]=node{u,v,head[u]};
- head[u]=cnt++;
- }
- int dfn[MAX];
- int low[MAX];
- int sta[MAX],top;
- int Scc[MAX];
- int in[MAX],out[MAX];
- void tardfs(int u,int &lay,int &sig)
- {
- low[u]=dfn[u]=lay++;
- sta[top++]=u;
- for(int i=head[u];~i;i=e[i].next)
- {
- int t=e[i].t;
- if(dfn[t]==0)
- {
- tardfs(t,lay,sig);
- low[u]=min(low[u],low[t]);
- }
- else if(!Scc[t])
- low[u]=min(low[u],dfn[t]);
- }
- if(low[u]==dfn[u])
- {
- sig++;
- while(1)
- {
- int j=sta[--top];
- Scc[j]=sig;
- if(j==u)break;
- }
- }
- }
- int tarjan(int n)
- {
- int sig=0;
- int lay=1;
- top=0;
- memset(Scc,0,sizeof(Scc));
- memset(dfn,0,sizeof(dfn));
- for(int i=1;i<=n;i++)
- {
- if(!dfn[i])tardfs(i,lay,sig);
- }
- return sig;
- }
- int main()
- {
- int n,m,u,v,T;
- cin>>T;
- while(T--)
- {
- cin>>n>>m;
- memset(head,-1,sizeof(head));
- cnt=0;
- while(m--)
- {
- scanf("%d%d",&u,&v);
- add(u,v);
- }
- int sig=tarjan(n);
- if(sig==1)puts("0");
- else
- {
- memset(in,0,sizeof(in));
- memset(out,0,sizeof(out));
- for(int i=1;i<=n;i++)
- {
- for(int j=head[i];~j;j=e[j].next)
- if(Scc[i]!=Scc[e[j].t])
- in[Scc[e[j].t]]=out[Scc[i]]=1;
- }
- int a=0,b=0;
- for(int i=1;i<=sig;i++)
- {
- if(in[i]==0)a++;
- if(out[i]==0)b++;
- }
- cout<<max(a,b)<<endl;
- }
- }
- }
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】:
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<iostream>
- #include<algorithm>
- using namespace std;
- const int MAX=202020;
- struct node{
- int s,t,next;
- }e[MAX];
- int head[MAX],cnt;
- void add(int u,int v)
- {
- e[cnt]=node{u,v,head[u]};
- head[u]=cnt++;
- }
- int dfn[MAX];
- int low[MAX];
- bool iscut[MAX];
- void tardfs(int u,int &lay,int fa)
- {
- low[u]=dfn[u]=lay++;
- int child=0;
- for(int i=head[u];~i;i=e[i].next)
- {
- int t=e[i].t;
- if(t==fa)continue;
- if(dfn[t]==0)
- {
- tardfs(t,lay,u);
- low[u]=min(low[u],low[t]);
- child++;
-
- if(u!=1&&low[t]>=dfn[u])
- iscut[u]=1;
- }
- else
- low[u]=min(low[u],dfn[t]);
-
- }
- if(u==1&&child>1)iscut[1]=1;
- }
- int tarjan(int n)
- {
- int sig=0;
- int lay=1;
- memset(iscut,0,sizeof(iscut));
- memset(dfn,0,sizeof(dfn));
- for(int i=1;i<=n;i++)
- {
- if(!dfn[i])tardfs(i,lay,-1);
- }
- for(int i=1;i<=n;i++)
- if(iscut[i])sig++;
- return sig;
- }
- int main()
- {
- int n,m,q,u,v;
- while(cin>>n,n)
- {
- memset(head,-1,sizeof(head));
- cnt=0;
- while(scanf("%d",&u),u)
- {
- do{
- scanf("%d",&v);
- add(u,v);
- add(v,u);
- }while(getchar()!='\n');
- }
- cout<<tarjan(n)<<endl;
- }
- }
例题,uva796 求桥数并输出
题意是求出桥的数目并输出。
【代码uva796】
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<iostream>
- #include<algorithm>
- using namespace std;
- const int MAX=202020;
- struct node{
- int s,t,next;
- }e[MAX];
- int head[MAX],cnt;
- void add(int u,int v)
- {
- e[cnt]=node{u,v,head[u]};
- head[u]=cnt++;
- }
- int dfn[MAX];
- int low[MAX];
- bool bri[MAX];
- node ans[MAX];
- void tardfs(int u,int &lay,int fa)
- {
- low[u]=dfn[u]=lay++;
- for(int i=head[u];~i;i=e[i].next)
- {
- int t=e[i].t;
- if(t==fa)continue;
- if(dfn[t]==0)
- {
- tardfs(t,lay,u);
- low[u]=min(low[u],low[t]);
- if(low[t]>dfn[u])
- bri[i]=1;
- }
- else
- low[u]=min(low[u],dfn[t]);
- }
- }
- int tarjan(int n)
- {
- int bridge=0;
- int lay=1;
- memset(bri,0,sizeof(bri));
- memset(dfn,0,sizeof(dfn));
- for(int i=1;i<=n;i++)
- {
- if(!dfn[i])tardfs(i,lay,-1);
- }
- for(int i=0;i<cnt;i++)
- if(bri[i])bridge++;
- return bridge;
- }
- bool cmp(node a,node b)
- {
- if(a.s==b.s)return a.t<b.t;
- return a.s<b.s;
- }
- int main()
- {
- int n,m,q,u,v;
- while(cin>>n)
- {
- memset(head,-1,sizeof(head));
- cnt=0;
- for(int i=1;i<=n;i++)
- {
- scanf("%d (%d)",&u,&q);u++;
- while(q--)
- {
- scanf("%d",&v);v++;
- add(u,v);
- add(v,u);
- }
- }
- printf("%d critical links\n",tarjan(n));
- int t=0;
- for(int i=0;i<cnt;i++)
- {
- if(bri[i])
- {
- if(e[i].s<e[i].t)
- ans[t++]=node{e[i].s,e[i].t};
- else ans[t++]=node{e[i].t,e[i].s};
- }
- }
- sort(ans,ans+t,cmp);
- for(int i=0;i<t;i++)
- printf("%d - %d\n",ans[i].s-1,ans[i].t-1);
- puts("");
- }
- }