tarjan算法 java_(poj2186)Tarjan算法,求强连通分量

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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值