虽然搬家了,但新家还是有点问题,写了这么半天的东西,先发到这里来吧。
其实以前求强连通分支都是用两次深搜,没有实现过Tarjan算法,其实写起来都差不多了。只是细节上容易出现错误。在以下的代码中我把容易错的地方都标注了出来,方便以后查看。。
先贴个两次深搜的代码:
其中第一次是在原图上进行深搜,第二次是在反向图中按照第一次深搜结束时间递减的顺序搜索。这样可以保证第二次搜索的时候每次找到一个强连通分支。
TWICE_DFS
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
vector<int>u[101];
vector<int>v[101];
bool s[101];
bool noout[101],noin[101];
int stack[101];
int belong[101];
int n,m,indx;
int dfs1(int t)
{
if(s[t])
return 0;
s[t]=1;
int i;
for(i=0;i<u[t].size();i++)
{
dfs1(u[t][i]);
}
stack[++stack[0]]=t;
return 0;
}
int dfs2(int t)
{
if(s[t])
return 0;
s[t]=1;
int i;
for(i=0;i<v[t].size();i++)
{
dfs2(v[t][i]);
}
belong[t]=indx;
return 0;
}
int main()
{
scanf("%d",&n);
int i,j;
for(i=1;i<=n;i++)
{
while(1)
{
scanf("%d",&m);
if(m==0)
break;
u[i].push_back(m);
v[m].push_back(i);
}
}
memset(s,0,sizeof(s));
for(i=1;i<=n;i++)
{
dfs1(i);
}
memset(s,0,sizeof(s));
for(i=stack[0];i>=1;i--)
{
if(!s[stack[i]])
{
indx++;
dfs2(stack[i]);
}
}
memset(noout,1,sizeof(noout));
memset(noin,1,sizeof(noin));
for(i=1;i<=n;i++)
{
for(j=0;j<u[i].size();j++)
{
if(belong[i]!=belong[u[i][j]])
{
noout[belong[i]]=0;
noin[belong[u[i][j]]]=0;
}
}
}
int res1=0,res2=0;
for(i=1;i<=indx;i++)
{
if(noout[i])
res2++;
if(noin[i])
res1++;
}
if(indx==1)
{
printf("1\n");
printf("0\n");
}
else
{
printf("%d\n",res1);
printf("%d\n",max(res1,res2));
}
system("pause");
return 0;
}
下面介绍一下Tarjan算法: Tarjan算法可以求无向图的块,割点,桥,也可以求有向图的强连通分支,实现的细节不大一样,但思想是一样的,都是利用了搜索树的性质,可以利用子节点的信息来更新父节点求得我们需要的信息。
low[i]代表由i节点可以达到的编号最小的结点,dfn[i]表示访问的编号,对于无向图,由于在无向图的搜索中不会出现交叉边,所以对节点i的子树进行搜索后,假设i的儿子是j,则如果low[j]>dfn[i],则(i,j)为桥,如果i不是根节点,且low[j]>=dfn[i]或者i是根节点,且儿子数>1,则i为割点。
在有向图中由于会出现交叉边,所以我们不能像在求无向图的连通分支那样去在有向图中进行搜索。怎样保证求得强连通分支呢,我们可以用一个栈来保存连通分支中的结点,当确定一个连通分支之后退栈。
怎么求得连接两个强连通分支的边呢?low[j]>dfn[i]?这样做是错误的,少考虑了一种情况。如果(i,j)是交叉边,即在搜索i之前j已经搜索过,且j不是i的父节点,则(i,j)也为连接两个强连通分支的边。
(写的我很没有信心,不知道上面的文字对不对,如有错误请各位指正)
代码如下:
Tarjan
#include<iostream>
#include<vector>
#define MAX 101
using namespace std;
vector<int>list[MAX];
int dfn[MAX],low[MAX];
int stack[MAX],instack[MAX];
int belong[MAX];
int Tuan=0;
int n;
int Index=0;
int indeg[MAX],outdeg[MAX];
int qiao[1000000][2];
int qiaonum;
int dfs(int u)
{
dfn[u]=low[u]=++Index;
int i;
stack[++stack[0]]=u;
instack[u]=1;
for(i=0;i<list[u].size();i++)
{
int v=list[u][i];
if(dfn[v]==0)
{
dfs(v);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])//如果没访问过v点,且low[v]>dfn[u]则边(u,v)为连接两个强连通分量的边
{
qiao[++qiaonum][0]=u;
qiao[qiaonum][1]=v;
}
}
else //若已经访问过v点
{
if(instack[v])//若不是交叉边
{
low[u]=min(low[u],dfn[v]);
}
else //若是交叉边:如果访问过v点,且dfn[v]<low[u]则边(u,v)为连接两个强连通分量的边
{
qiao[++qiaonum][0]=u;
qiao[qiaonum][1]=v;
}
}
}
if(low[u]==dfn[u])
{
Tuan++;
int j;
do{
j=stack[stack[0]--];
instack[j]=0;
belong[j]=Tuan;
}while(j!=u);
}
return 0;
}
int main()
{
scanf("%d",&n);
int i,j;
for(i=1;i<=n;i++)
{
while(scanf("%d",&j)!=EOF&&j)
{
list[i].push_back(j);
}
}
for(i=1;i<=n;i++)
{
if(dfn[i]==0)
dfs(i);
}
for(i=1;i<=qiaonum;i++)
{
if(belong[qiao[i][0]]!=belong[qiao[i][1]])
{
outdeg[belong[qiao[i][0]]]++;
indeg[belong[qiao[i][1]]]++;
}
}
int cnt1=0,cnt2=0;
for(i=1;i<=Tuan;i++)
{
if(outdeg[i]==0)
cnt1++;
if(indeg[i]==0)
cnt2++;
}
if(Tuan==1)
{
printf("1\n");
printf("0\n");
}
else
{
printf("%d\n",cnt2);
printf("%d\n",max(cnt1,cnt2));
}
system("pause");
return 0;
}
PS:大家可以去看看
CmYkRgB123的讲解,很详细。 比我讲的好。。。