一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法。 ------百度百科
解读一下这句话,Tarjan算法可以解决存在强连通分量的图,而且是在线性时间内解决。所以,不得不%一下Tarjan,聪明的脑子提供了这么一个神奇的算法。
网上关于Tarjan算法(以下简称tarjan)的博客琳琅满目,这里只记录一下我对tarjan的理解,然后就当是做个笔记了。
首先所需要的知识储备:DFS,有向图,树,栈,强连通,强连通图,强连通分量,解答树。
不会怎么办?有办法,我的博客已经跟百度达成了合作协议,从现在开始,不会的问题都可以直接进行百度了,也算是我的一点小小的贡献吧,贴个链接:百度一下
简单说,tarjan就是一个DFS的过程,首先需要两个数组来完成这个任务,一个时dfn数组,他的任务是记录搜索到此时顺序的编号,就是第几个被搜到的,其实就是DFS序啦。然后用数组low来记录每个点在这棵树中最小子树根,可能听起来不那么容易懂,就是在某个子树中,dfs序编号最小的那个,所以初始的low[i]==dfn[i],深度遍历一棵子树,到达叶子节点后返回,此时去取值low的min值,若是low[i] == dfn[i]的话,此时的i节点,就是强连通分量的一个根,将每一个遍历得到的节点入栈,得到low[i] == dfn[i]时,就将此节点之后入栈的都出栈,此时就得到一个强连通分量。
注意强连通分量不止一个,所以要遍历每一个节点,当dnf[i] == 0 的时候就可以进行一次tarjan运算了。
还是不懂的话,就放一张烂大街的图片吧,好多博客都拿这个模拟整个过程。
1. 从1开始,dfn[1] = low[1] = 1,此时栈内元素:1
2. 遍历到2,dfn[2] = low[2] = 2,此时栈内元素:1,2
3. 遍历到3,dfn[3] = low[3] = 3,此时栈内元素:1,2,3
4. 遍历到6,dfn[6] = low[6] = 4,此时栈内元素:1,2,3,6
然后发现6的出度为0,证明此时到达叶子节点,判断此时dfn[6] = low[6],说明此节点是强连通分量的根节点,然后6即以后的 点出栈,此时栈内元素:1,2,3
5. 回溯到3,3已经无法再深度遍历,判断dfn[3] = low[3],同节点6操作,此时栈内元素:1,2
6. 回溯到2,继续遍历到5,dfn[5] = low[5] = 5,此时栈内元素:1,2,5
7. 遍历到1,1还在栈内,然后更新low[5] = 1,此时栈内元素:1,2,5
8. 遍历到2,2还在栈内,更新low[2] = 1,此时栈内元素:1,2,5,然后发现5跟2的low值都已经变为1,容易发现1,2,5就是 一个强连通分量呀。
9. 继续从1遍历到4,dfn[4] = low[4] = 6,此时栈内元素:1,2,5,4 此时所有节点都已经遍历
10. 4回到1,判断dfn[1] = low[1] = 1,然后1之后的所有元素出栈,那么,栈空了。
然后,收工。
放一个模板题练练手,HDU 1269 迷宫城堡
此题简直就是赤裸裸的tarjan,看到上面的模拟过程,可以在low[i] = dfn[i] 的时候来记录连通分量的个数,判断最后是否等于1就可以了。
代码实现:(偷来的模板)
/*
Look at the star
Look at the shine for U
*/
#include
#define sl(x) scanf("%d",&x)
#define PII pair
using namespace std;
const int INF = 0x3f3f3f;
const int N = 1e6+5;
const int mod = 1e9+7;
struct node{
int v,next;
}p[N];
int head[N],dfn[N],low[N],cnt,n,m,sta[N],top,Scc[N];
void add(int u,int v){p[cnt].v = v;p[cnt].next = head[u];head[u] = cnt++;}
void init()
{
for(int i = 0;i <= n;i++) head[i] = -1;
cnt = 0;top = 0;
}
void tardfs(int u,int &lay,int &sig)
{
low[u] = dfn[u] = lay++;
sta[top++] = u;
for(int i = head[u];~i;i = p[i].next)
{
int t = p[i].v;
if(!dfn[t])
{
tardfs(t,lay,sig);
low[u] = min(low[u],low[t]);
}
else if(!Scc[t])
low[u] = min(low[u],dfn[t]);
}
if(low[u] == dfn[u])
{
sig++;
while(1)
{
int j = sta[--top];
Scc[j] = sig;
if(j == u)break;
}
}
}
int tarjan()
{
int sig = 0;
int lay = 1;
top = 0;
memset(Scc,0,sizeof(Scc));
memset(dfn,0,sizeof(dfn));
for(int i = 1;i <= n;i++)
{
if(!dfn[i])tardfs(i,lay,sig);
}
return sig;
}
int main()
{
int i,j,k,x,y;
while(~sl(n))
{
sl(m);
if(!n && !m) return 0;
init();
for(i = 0;i < m;i++) sl(x),sl(y),add(x,y);
int flag = tarjan();
if(flag == 1) puts("Yes");
else puts("No");
}
}
POJ 2186 Popular Cows
有n个顶点,问有几个顶点由任何顶点出发都能到达。
Easy,强连通缩一下点,就成了连通分量构成的DAG,然后判断每一个连通分量的出度,如果只有一个出度为0,说明其他的都可以走到他,答案就是这个连通分量内的顶点数,若是有 != 1的连通分量,说明有两个连通分量不可互相到达,那么答案就是0,tarjan中的Scc数组可以完成判断出入度的操作。
代码实现:(头文件POJ不支持,但我就是不改)
/*
Look at the star
Look at the shine for U
*/
#include
#define sl(x) scanf("%d",&x)
#define PII pair
using namespace std;
const int INF = 0x3f3f3f;
const int N = 1e6+5;
const int mod = 1e9+7;
struct node{
int v,next;
}p[N];
int head[N],dfn[N],low[N],cnt,n,m,sta[N],top,Scc[N];
void add(int u,int v){p[cnt].v = v;p[cnt].next = head[u];head[u] = cnt++;}
void init()
{
for(int i = 0;i <= n;i++) head[i] = -1;
cnt = 0;top = 0;
}
void tardfs(int u,int &lay,int &sig)
{
low[u] = dfn[u] = lay++;
sta[top++] = u;
for(int i = head[u];~i;i = p[i].next)
{
int t = p[i].v;
if(!dfn[t])
{
tardfs(t,lay,sig);
low[u] = min(low[u],low[t]);
}
else if(!Scc[t])
low[u] = min(low[u],dfn[t]);
}
if(low[u] == dfn[u])
{
sig++;
while(1)
{
int j = sta[--top];
Scc[j] = sig;
if(j == u)break;
}
}
}
int tarjan()
{
int sig = 0;
int lay = 1;
top = 0;
memset(Scc,0,sizeof(Scc));
memset(dfn,0,sizeof(dfn));
for(int i = 1;i <= n;i++)
{
if(!dfn[i])tardfs(i,lay,sig);
}
return sig;
}
int sum[N];
void solve()
{
int i,j,k;
int t = tarjan();
for(i = 1;i <= n;i++)
{
for(j = head[i];~j;j = p[j].next)
{
int v = p[j].v;
if(Scc[i] != Scc[v]) sum[Scc[i]]++;
}
}
int s = 0;
for(i = 1;i <= t;i++)
{
if(!sum[i])
{
s++;
k = i;
}
}
if(s == 1)
{
int ans = 0;
for(i = 1;i <= n;i++) if(Scc[i] == k) ans++;
printf("%d\n",ans);
}
else puts("0");
}
int main()
{
int i,j,k,x,y;
while(~sl(n))
{
sl(m);
init();
for(i = 0;i < m;i++) sl(x),sl(y),add(x,y);
solve();
}
}
偷偷高兴一会,又对一种高深的算法懂了点皮毛,最近还算高效,道阻且长,任重而道远。