学习目录:1.总结tarjian算法解决强连通分量问题的相关知识。
2.开始复习以前的知识。
一.tarjian算法解决强连通分量
强连通:有向图中,从任意点i可到达任意点j。
强连通分量:在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做强连通分量。
tarjian算法其实就是用dfs实现的,每个强连通分量为搜索树中的一棵子树。
在实现时我们需要两个重要的数组:
dfn[]:即时间戳,表示他在dfs(tarjan)中是第几个被搜到的。可以依据dfn[]是否等于0来判断该点是否被访问过。
low[]表示该点通过有向边可回溯的最早的时间戳(即dfn[])。
在通过有向边回溯前,该点的low[]=dfn[]。
步骤:
首先就是按照深度优先搜索算法搜索的次序对图中所有的节点进行搜索,并且将访问的点压入栈中,故在这里需要一个book数组来记录该点是否在栈中。在搜索过程中,对于任意节点i和与其相连的节点j。
先看该点j是否被访问过(通过dfn来判断),如果没有,那么就继续对j进行深度搜索,直到找到底,回溯时每次都比较一下子节点与这个节点的low值,如果子节点的low值更小,取子节点的low值,这说明该点可通过子节点回溯到更早的点。
如果该点j被访问过,看它是否在栈中,如果在栈中,说明我也有机会借助该点回溯到更早的点,我比较一下点i和点j的dfn(low值在这里也行)值,取更小值。
当我把节点i和与其相连的所有节点j都遍历完后,我的dfn[]还是等于low[],说明我无法回溯到更早的点,且这个节点是这个强连通分量的根节点。我们将这个栈(先进后出)里,比此节点后进来的节点全部出栈,它们就组成一个全新的强连通分量。
如下图(来源于biibii邋遢大哥):
代码实现如下:
#include<bits/stdc++.h>
using namespace std;
struct Edge
{
int to,next;
}edge[500010];
int cnt,head[100010],dfn[100010],low[100010],book[100010],stackk[100010];//dfn[]:即时间戳,表示他在dfs(tarjan)中是第几个被搜到的,low[]表示该点通过有向边可回溯的最早的时间戳(dfn数组)
//链式前向星
void add(int a,int b)
{
cnt++;
edge[cnt].to=b;
edge[cnt].next=head[a];
head[a]=cnt;
}
int top,timee,ans;
void tarjan(int x)
{
timee++;
dfn[x]=low[x]=timee;
top++;
stackk[top]=x;
book[x]=1;//标记在栈中
for(int i=head[x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if(dfn[y]==0)//该节点未被访问过
{
tarjan(y);
low[x]=min(low[y],low[x]);
}
else if(book[y]==1)
{
low[x]=min(low[y],dfn[x]);
}
}
if(low[x]==dfn[x])
{
ans++;//ans表示有几个强连通分量
while(stackk[top]!=x)
{
book[stackk[top]]=0;//标志其不在栈中
top--;
}
book[stackk[top]]=0;
top--;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
head[i]=-1;
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);//有一条从x到y的边
add(x,y);
}
for(int i=1;i<=n;i++)
if(dfn[i]==0)tarjan(i);
printf("%d",ans);
return 0;
}