Tarjan算法,求强连通分量
深度优先搜索,访问一个节点,给节点赋一个访问顺序的值,给他的low也赋同样的预先值,再用一个堆栈记录它,接着继续访问未访问的节点,如果子节点的low值小于它的,就更新之;如果它能访问的节点在堆栈中,就用那个点的low值更新它。现在,如果访问顺序和他的low值相等,就从堆栈里面pop,知道pop到该节点,并把这些节点归为一类,它们就是一个强连通分图。
整个代码很清晰:
void tarjan(int
i){
int j;
dfn[i]=low[i]=++cnt;
//cnt++;
isin[i]=1;
sta.push(i);
for(j=0;j
int nn=g[i][j];
if(dfn[nn]==0){//该子节点未被访问
tarjan(nn);
if(low[nn]
low[i]=low[nn];
}
if(isin[nn]==1&&low[nn]
low[i]=low[nn];
}
}
if(low[i]==dfn[i]){//出堆栈,把这些点归为一个强连通分量中
bno++;
do{
j=sta.top();
sta.pop();
isin[j]=false;
bel[j]=bno;
}while(j!=i);
}
}
但是poj2186不仅如此,而且要求能被所有点访问的点的个数。求出强连通分图后,把每个强连通分图缩成一个点后,构成的图肯定不含圈,所以这个构图时一个DAG。DAG中能被所有点访问的点只有目标点了,也就是那些出度为0的点。所以下一步的工作就是找出所有出度为0的强连通分量对应的缩点,然后看是否唯一,不唯一说明无解,唯一的话,解为该连通分量中顶点的个数。
仔细体会其中的思想,求强连通分量,再缩点,再根据新构图的性质求解。这个思想似乎很常用,我上一篇博客中那个加边去桥那题也用到了这个思想,那个是求“扩展的点共圈分支”,缩点构图,根据形成的树的性质,找出至少要加的边数为叶子数/2。之所以要找子图,缩点,是因为找的的子图满足一定的性质,以至于他们能抽象成一个单独的整体,使得问题简化。譬如前者,强连通分量满足其中的元素互相可达,于是把它当成一个整体,整体对另一个整体可达,那么整体中的元素对另一个整体的元素也可达;再看后者,“扩展的点共圈分子”满足其中的元素都满足“扩展的共圈关系”,把其当成一个整体,整体间“共圈”,每个元素也共圈了。
在分析那些“分图”中的关系,这些关系都是等价关系。那个强连通关系,不是二元关系,是多元关系,但这个多元关系仍满足等价关系;扩展的点共圈亦是如此,满足等价。所以,需要考虑的问题,涉及到的元素间的关系是等价关系时,可考虑求“分图”,缩点抽象构图,在进一步求解。
其实这两道题给我的启发不是tarjan算法及其变种,而是这种求分图缩点化简的思路。
最后给出poj2186的代码:
#include "stdio.h"
#include "stack"
#include "vector"
using namespace
std;
#define N 10024
vector g[N];
int dfn[N];
int low[N];
int isin[N];
int bel[N];
int bno;
int n,m;
stack sta;
int cnt;
void tarjan(int
i){
int j;
dfn[i]=low[i]=++cnt;
//cnt++;
isin[i]=1;
sta.push(i);
for(j=0;j
int nn=g[i][j];
if(dfn[nn]==0){//该子节点未被访问
tarjan(nn);
if(low[nn]
low[i]=low[nn];
}
if(isin[nn]==1&&low[nn]
low[i]=low[nn];
}
}
if(low[i]==dfn[i]){//出堆栈,把这些点归为一个强连通分量中
bno++;
do{
j=sta.top();
sta.pop();
isin[j]=false;
bel[j]=bno;
}while(j!=i);
}
}
int solve(){
int i;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(bel,-1,sizeof(bel));
memset(isin,0,sizeof(isin));
bno=0;
cnt=0;
for(i=0;i
if(dfn[i]==0)//对每个未访问的点dfs
tarjan(i);
}
return bno;
}
int is[N];//该分量出度是否不为0
int tongji[N];//各分量的顶点数
int dfs(){
int i,j;
memset(is,0,sizeof(is));
for(i=0;i
for(j=0;j
int nn=g[i][j];
if(bel[i]!=bel[nn]){
is[bel[i]]=1;
}
}
}
int cnt=0;
int no=-1;
for(i=1;i<=bno;i++){//统计出度为0的顶点个数
if(is[i]==0){
cnt++;
no=i;
}
}
if(cnt==1)
return no;
else
return -1;
}
int main(){
int i,j;
int a,b;
scanf("%d%d",&n,&m);
for(i=0;i
scanf("%d%d",&a,&b);
g[a-1].push_back(b-1);
}
int ret=0;
solve();
memset(tongji,0,sizeof(tongji));
for(i=0;i
tongji[bel[i]]++;
}
ret=dfs();
if(ret>0)
printf("%d\n",tongji[ret]);
else
printf("0\n");
return 0;
}