(本人码字困难户)
Tarjan
前几天联训刚刚学习了 Tarjan 算法;
处女作,第一次鼓起勇气厚着脸皮post csdn,可能有些理解不透彻,希望各位大神指点江山。
顺便说一句,有一些图论的题需要我们去求解环的问题,而此时就会有两种算法供我们选择 ; 一种是 Tarjan ,一种是阿三算法—— Kasoraju ; 他们都是通过dfs实现的 ;
Kasoraju就不写了,看看Tarjan ;
tarjan算法适用于求强连通分量,所以它本身板子的只能用于求有向图的强连通分量;对于强连通分量,可以感性的认为它是一个环;
下面是自己写的 Tarjan 算法:
#include<bits/stdc++.h>
using namespace std;
const int N=10000+5;
int g[N][N],dfn[N],low[N],s[N],cn,t,n,x,y,m,k;
bool ins[N];
void tarjan(int x){
int j;
cn++;
dfn[x]=low[x]=cn;
t++;s[t]=x;ins[x]=true;
for(int i=1;i<=g[x][0];i++){
j=g[x][i];
if(!dfn[j]){
tarjan(j);
low[x]=min(low[x],low[j]);
}
else if(ins[j]) low[x]=min(low[x],dfn[j]);
}
if(dfn[x]==low[x]){
cout<<"强连通分量 #"<<++k<<" : ";
while(s[t]!=x){
ins[t]=false;
cout<<s[t]<<" ";
t--;
}
ins[x]=false;
t--;
cout<<x<<endl;
}
return ;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>x>>y;
g[x][++g[x][0]]=y;
}
tarjan(1);
return 0;
}
对于g[x][i] , 表示第x个点的第i个出点;
对于m,代表边数;
对于dfn[i],表示第i个点进栈的时间(被dfs到的时间),也就是众人所说的时间戳;
对于low[i],表示第i个点进过一条非树边能够到达的点的时间戳;
对于s[t]=x,表示的是栈顶的元素是x;
对于ins[x]数组,表示的是元素x在不在栈中;
解释一下low数组的意义:
想象一下我们深陷迷雾之中;
从一个点开始走着,走着,并且我们只能向前看...
规定假如这两个点连通,那么我们站在该点能看见那另一个点;否则看不到;
每向前走一步如果我们可以看见原来走过的点,那么这显然就是一个环,不是吗?
这个low值记录的就是能够看到的走过最早的点;
显然我们可以看到我们正站着的点,所以low的初值是该点dfn的值;(当然不幸的是我正在迷雾中寻找着那个走过的点,可从来回不去...)
最后是弹栈的过程:
if(dfn[x]==low[x]){
cout<<"强连通分量 #"<<++k<<" : ";
while(s[t]!=x){
ins[t]=false;
cout<<s[t]<<" ";
t--;
}
ins[x]=false;
t--;
cout<<x<<endl;
}
首先如果我在当前点除了先前看看不到前缀点了,表示我本身或者与我连通的点是强连通分量(dfn[i]==low[i])!
栈会控制该点是否是单独的强连通分量(当它是栈顶元素时).因为如果它有后缀结点,后缀结点却没有出栈,表明后缀结点的dfn与low一定不同,而它后面那个结点(Y)的low值一定是小于等于它(Y)的dfn值的,但是它(Y结点)没有弹栈,说明它的dfn值 与 我当前所在节点(令其名曰:T)的low值的关系一定是:
dfn [ T ] >= low[ Y ]
说明我当前节点一定在某一个大小大于1的强连通分量中!
但是可能你会问:前面的点怎么弹呢?
首先我们可以确定,如果它前面还有强连通分量中的点,它是不会被弹栈的,因为它的dfn值一定不等于low值;
当然只是纸上谈兵;捧上几个栗子;
test#1
5 6
1 2
2 3
3 4
4 5
5 3
3 2
test#2
5 6
1 2
2 3
3 4
4 5
5 3
3 2
如果有什么特殊情况,或是有错误的,欢迎来捶我的胸口qwq;
相信你们也不怎么想捶qwq;
感谢Nationalnight的精彩传授;
ps:推荐读者去看一下wans的博客,别忘了给她留个赞,谢谢;