一、知识储备
强连通: 在一个有向图G里,设两个点 a 、b,发现由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通。
强连通图: 如果 在一个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。
强连通分量:在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就把这个子图叫做强连通分量。
参考博客:强连通算法——Tarjan个人理解+讲解
参考博客:全网最详细tarjan算法讲解
二、算法描述
tarjan算法,之所以用DFS,是因为它将每一个强连通分量作为搜索树(即有向图)上的一个子树。而这个图,就是一个完整的搜索树。
为了使这颗搜索树在遇到强连通分量的节点的时候能顺利进行。
每个节点都有两个参数(用数组表示):
① DFN[] 记录搜索到该点的时间,也就是第几个搜索这个点的;
② LOW[]记录该点所在的(强连通分量扮演的)搜索子树的根节点的DFN值。
过程:
- 数组的初始化:当首次搜索到点p时,DFN与LOW数组的值都为到该点的时间;
- 堆栈:每搜索到一个点,将它压入栈顶;
- 当点p有与点p’相连时,如果此时(时间为DFN[p]时)p’不在栈中,p的low值为两点的low值中较小的一个;
- 当点p有与点p’相连时,如果此时(时间为DFN[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个;
- 当搜索到一个点的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量;
- 继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
帮助领悟:
- 我们用LOW值记录该点所在强连通子图对应的搜索子树的根节点的DFN值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方;
- 强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量;
- 如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量;
- 当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点;
- Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。
代码:
void tarjan(int u) //此代码仅供参考
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)Tarjan(v);
if(vis[v]==1)low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
sig++;
}
}
三、代码实现
#include<stdio.h>//此代码仅供参考,用于求一个图存在多少个强连通分量
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define maxn 1000000
vector<int >mp[maxn];
int ans[maxn];
int vis[maxn];
int dfn[maxn];
int low[maxn];
int n,m,tt,cnt,sig;
void init()
{
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)mp[i].clear();
}
void Tarjan(int u)
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)Tarjan(v);
if(vis[v]==1)low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
sig++;
}
}
void Slove()
{
tt=-1;cnt=1;sig=0;
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
{
Tarjan(i);
}
}
printf("%d\n",sig);
}
int main()
{
while(~scanf("%d",&n))
{
if(n==0)break;
scanf("%d",&m);
init();
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mp[x].push_back(y);
}
Slove();
}
}
//测试数据
/*
7 9
1 2
2 3
3 1
2 4
4 7
7 4
4 5
5 6
6 4
8 10
1 2
2 3
3 1
2 4
4 7
7 4
4 5
5 6
6 4
7 8
*/
四、沙场练兵
题目一、迷宫城堡
题目二、Popular Cows