Hello Tarjan ---- Tarjan算法小结

一种由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运算了。

还是不懂的话,就放一张烂大街的图片吧,好多博客都拿这个模拟整个过程。
 

Hello Tarjan ---- Tarjan算法小结_第1张图片

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();
	}
}

偷偷高兴一会,又对一种高深的算法懂了点皮毛,最近还算高效,道阻且长,任重而道远。

你可能感兴趣的:(******算法******,******图论******,ACM的进阶之路)