理解tarjan算法求强连通分量
2012-07-07 08:31
260 查看
tarjan算法的基本框架就是dfs,其基本原理是有向图至少存在一棵深搜子树,其结点集合构成一个强连通分量,这是显然的,因为必定有一个强连通分量最后被dfs,这个强连通分量的结点构成深搜树的一棵子树。
有了以上结论后,求强连通分量就有思路了,我们在每棵子树深搜完成后判断这棵子树是否构成强连通分量即可,关键在于如何判断一棵子树是否构成强连通分量。
注意到最先搜索完的子树是那些叶子结点,要判断叶子结点是否构成强连通分量很简单,若存在叶子结点与其祖先结点的连边,则该叶子结点不构成强连通分量,否则构成强连通分量。tarjan算法用pre[V]数组和low[V]数组来判断子树是否构成强连通分量,pre[v]保存结点v在先序遍历中的访问顺序,以下统称深度优先数,low[v]保存从v出发能到达的所有结点的最小深度优先数,用叶子结点来解释,v为叶子结点,
当low[v]<pre[v]时,表明存在v到其祖先结点的连边,v不构成强连通分量;
当low[v]==pre[v]时,表明不存在v到其祖先结点的连边,v构成强连通分量。
若深搜树中存在一个叶子结点构成强连通分量,则通过以上判断可以求出,也即求出了第一个强连通分量;
若深搜树中所有叶子结点都不构成强连通分量,此时叶子结点必定与其父结点同在一个强连通分量中,可以将其缩到父结点中(具体操作是更新父结点的low数组),原来的父结点就变成了“大”叶子结点,还是通过low[v]=?pre[v]来判断这个“大”叶子结点是否构成强连通分量,一直这样下去,叶子结点将越来越大,直到求出第一个强连通分量为止。
在求出第一个强连通分量后,我们将其包含的结点在原图中删除,问题又转化成了求第一个强连通分量的问题,理解还是一样的,以此类推,直到所有的强连通分量均被求出,此时算法结束。
参考代码:
#include <stdio.h>
#include <string.h>
#include <vector>
#define MIN(a,b) ((a)<(b)?(a):(b))
using namespace std;
#define N 100
int pre
,low
,id
,s
,t,cnt,top,n,m;
vector<int> g
;
//初始化
void init()
{
t=cnt=top=0;
memset(pre,0xff,sizeof(pre));
}
//tarjan算法主体
void dfs(int u)
{
int min=pre[u]=low[u]=t++;
int i,v;
s[top++]=u;
for(i=0;i<g[u].size();i++)
{
v=g[u][i];
if(pre[v]==-1) dfs(v);
min=MIN(min,low[v]);
}
if(min<low[u])
{
low[u]=min;
return;
}
do{
id[v=s[--top]]=cnt;
low[v]=n;
}while(v!=u);
cnt++;
}
//调用测试
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int u,v,i,kase=0;
while(~scanf("%d%d",&n,&m))
{
for(i=0;i<n;i++) g[i].clear();
for(i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
init();
for(i=0;i<n;i++)
{
if(pre[i]==-1) dfs(i);
}
for(i=0;i<cnt;i++) g[i].clear();
for(i=0;i<n;i++)
{
g[id[i]].push_back(i);
}
printf("Case %d: ",++kase);
for(i=0;i<cnt;i++)
{
printf("{ ");
for(int j=0;j<g[i].size();j++)
{
printf("%d ",g[i][j]);
}
printf("} ");
}
printf("\n");
}
return 0;
}
有了以上结论后,求强连通分量就有思路了,我们在每棵子树深搜完成后判断这棵子树是否构成强连通分量即可,关键在于如何判断一棵子树是否构成强连通分量。
注意到最先搜索完的子树是那些叶子结点,要判断叶子结点是否构成强连通分量很简单,若存在叶子结点与其祖先结点的连边,则该叶子结点不构成强连通分量,否则构成强连通分量。tarjan算法用pre[V]数组和low[V]数组来判断子树是否构成强连通分量,pre[v]保存结点v在先序遍历中的访问顺序,以下统称深度优先数,low[v]保存从v出发能到达的所有结点的最小深度优先数,用叶子结点来解释,v为叶子结点,
当low[v]<pre[v]时,表明存在v到其祖先结点的连边,v不构成强连通分量;
当low[v]==pre[v]时,表明不存在v到其祖先结点的连边,v构成强连通分量。
若深搜树中存在一个叶子结点构成强连通分量,则通过以上判断可以求出,也即求出了第一个强连通分量;
若深搜树中所有叶子结点都不构成强连通分量,此时叶子结点必定与其父结点同在一个强连通分量中,可以将其缩到父结点中(具体操作是更新父结点的low数组),原来的父结点就变成了“大”叶子结点,还是通过low[v]=?pre[v]来判断这个“大”叶子结点是否构成强连通分量,一直这样下去,叶子结点将越来越大,直到求出第一个强连通分量为止。
在求出第一个强连通分量后,我们将其包含的结点在原图中删除,问题又转化成了求第一个强连通分量的问题,理解还是一样的,以此类推,直到所有的强连通分量均被求出,此时算法结束。
参考代码:
#include <stdio.h>
#include <string.h>
#include <vector>
#define MIN(a,b) ((a)<(b)?(a):(b))
using namespace std;
#define N 100
int pre
,low
,id
,s
,t,cnt,top,n,m;
vector<int> g
;
//初始化
void init()
{
t=cnt=top=0;
memset(pre,0xff,sizeof(pre));
}
//tarjan算法主体
void dfs(int u)
{
int min=pre[u]=low[u]=t++;
int i,v;
s[top++]=u;
for(i=0;i<g[u].size();i++)
{
v=g[u][i];
if(pre[v]==-1) dfs(v);
min=MIN(min,low[v]);
}
if(min<low[u])
{
low[u]=min;
return;
}
do{
id[v=s[--top]]=cnt;
low[v]=n;
}while(v!=u);
cnt++;
}
//调用测试
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int u,v,i,kase=0;
while(~scanf("%d%d",&n,&m))
{
for(i=0;i<n;i++) g[i].clear();
for(i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
init();
for(i=0;i<n;i++)
{
if(pre[i]==-1) dfs(i);
}
for(i=0;i<cnt;i++) g[i].clear();
for(i=0;i<n;i++)
{
g[id[i]].push_back(i);
}
printf("Case %d: ",++kase);
for(i=0;i<cnt;i++)
{
printf("{ ");
for(int j=0;j<g[i].size();j++)
{
printf("%d ",g[i][j]);
}
printf("} ");
}
printf("\n");
}
return 0;
}
相关文章推荐
- 关于强连通分量的一些理解(tarjan版本)
- hdu 1269 迷宫城堡(强连通分量 Tarjan算法)
- ccf认证题-高速公路(tarjan算法,求强连通分量)
- POJ1523(求连用分量数目,tarjan算法原理理解)
- Tarjan算法-强连通分量-题集
- 关于tarjan算法的理解
- Tarjan算法(求强连通分量)
- Tarjan算法 强连通分量
- bzoj2140对tarjan算法的一些理解
- Tarjan求有向图的强连通分量(Tarjan算法描述)
- 【强连通分量】tarjan算法及kosaraju算法+例题
- 图论——强连通分量:Tarjan算法。
- poj 1236 Network of Schools(强连通分量 Tarjan算法)
- Tarjan算法(求强连通分量)
- Tarjan算法的学习——求强连通分量
- 强连通分量的Tarjan算法
- hdu1269 迷宫城堡 强连通分量 tarjan算法(前向星实现)
- poj1236 Network of Schools ,有向图求强连通分量(Tarjan算法),缩点
- Tarjan算法求有向图的强连通分量
- 有向图的强连通分量(Tarjan算法模板)