强连通分量:
强连通图是指,对于图 G 中的每一对顶点 u, v,它们之间存在互相可达的两条路径:u…v 和 v …u。有向图 G 的强连通分量是指 G 的极大强连通子图。如果将每一个强连通分量缩成一个点,则原图 G 将会变成一张有向无环图(DAG)。
Tarjan算法:
任选一顶点开始进行深度优先搜索(若深度优先搜索结束后仍有未访问的顶点,则再从中任选一点再次进行)。搜索过程中已访问的顶点不再访问。搜索树的若干子树构成了图的强连通分量。
个人理解:
通过dfs算法的特性,找到某个点所能回溯到之前的哪一个点,当回溯到的某个点再也无法回溯的时候,那就从栈顶保存的点中一个一个得到相应的值,直到取到x==s.top为止(x是指low[x]==dfn[x]的点)为止我们姑且将这个点看做是强联通分量的一个根节点。从栈顶取到你之前回溯到的那个点(这两点之间的点包含这两点,即强联通分量的点集)
结合题目来整理一下tarjan算法的模板
题目链接:NOIP2015信息传递
题目思路:
借助tarjan求出强联通分量的最小的子集。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
vector<int>v[maxn];//邻接表
stack<int>s;
int dfn[maxn],low[maxn],tot,ans=maxn,ins[maxn];
//dfn是否为0可以判断点是否访问过,ins数组用来判断点是否在栈中
//dfn数组表示顶点dfs的时间戳,low[]为u能够追溯到的最早的栈中顶点的次序号
int n,m;
void readin()
{
int x,y;
tot=0;
fill(ins,ins+maxn,0);
fill(dfn,dfn+maxn,0);
scanf("%d",&n);
for(int i=1;i<=n;i++) v[i].clear();
for(int i=1;i<=n;i++){
scanf("%d",&x);
v[i].push_back(x);
}
}
void tarjan(int x)
{
low[x]=dfn[x]=++tot;//
s.push(x);ins[x]=1;
for(int i=0;i<v[x].size();i++){
int p=v[x][i];
if(!dfn[p]){//判断点是否被访问过
tarjan(p);
low[x]=min(low[x],low[p]);
}
else if(ins[p]) low[x]=min(low[x],dfn[p]);//判断点是否在栈中
}
if(low[x]==dfn[x]){//dfs到叶子节点,开始判断,如果==就到达了某个强联通分量的根节点,dfs回溯到了这个点
int cnt=0;
while(1){//得出相关的强联通分量
int now=s.top();
s.pop();
ins[x]=0;//出栈visit置0
cnt++;
if(now==x) break;//通过栈找到那个点
}
if(cnt>1) ans=min(ans,cnt);
}
}
int main()
{
readin();
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
cout<<ans<<endl;//ans为最小环的大小
}