Tarjan算法

Tarjan算法是一种求图的强连通分量的一种算法。首先我们介绍一些基本知识。
**强连通:**在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。极大强连通子图:G 是一个极大强连通子图当且仅当 G 是一个强连通子图且不存在另一个强连通子图 G’使得 G 是 G’的真子集。也就是说不可能找到一个包含它的强连通分量。若将有向图中的强连通分量都缩为一个点,则原图会形成一个 DAG(有向无环图)。

Tarjan算法_第1张图片
Tarjan 算法是基于对图深度优先搜索(DFS)的算法,每个强连通分量为搜索树中的一棵子树(的一部分)。搜索时,把当前搜索树中未处理的结点加入一个栈,对于每一个结点,当它自己搜索完毕时判断是否存在强连通分量。
DFS 过程中遇到的四种边:
树枝边:DFS 时经过的边,即 DFS 搜索树上的边
前向边:与 DFS 方向一致,从某个结点指向其某个子孙的边
后向边:与 DFS 方向相反,从某个结点指向其某个祖先的边
横叉边:从某个结点指向搜索树中另一子树中的某结点的边
定义 DFN(u)为结点 u 搜索的次序编号(时间戳), Low(u)为 u 或 u 的子树( 经过最多一条后向边或栈中横叉边) 能够回溯到的最早的栈中结点的次序号。 由定义可以得出:
Low(u)=Min
{
DFN(u),
Low(v),(u,v)为树枝边, u 为 v 的父结点
DFN(v),(u,v)为后向边或指向栈中结点的横叉边
}
当结点 u 的搜索过程结束后, 若 DFN(u)=Low(u), 则以 u 为根的搜索子树上所有还在栈中的结点是一个强连通分量。u及以后开始退栈,成为一个强连通分量,然后继续搜。Tarjan算法_第2张图片
此图便是一个经过Tarjan算法搜索后的一个有向图。由图可知1、3、4、2是一个强连通分量。而5和6分别是单独的一个强连通分量。搜索过程:搜索1,3 ,5,6(同时在入栈)发现6的dfn和low值一样退栈,5同理;继续搜索4入栈,4一条边(后向边)指向1,所以low【4】为1,而指向6是一条栈外横叉边,不更新,然后返回更新low【3】=1(3-4为树枝边),然后回到1,搜索2(入栈),4在栈中所以更新low【2】=5(2-4为栈中横叉边)。发现1的dfn=low,所以1及以后的都要退栈,1、3、4、2为一个强连通分量。由此推导过程可知:更新low值就如同上面所给的有两种情况:一、若边为树枝边则由它本身的dfn值和此树枝边对应的子节点的low值作比较选较小的;二、若为栈内横叉边或后向边则由其本身的dfn值和此边对应子节点的dfn值选较小的。
附Tarjan算法的关键代码(dfs过程):

void dfs(int u)
{
	times++;//记录dfn顺序 
	dfn[u]=times;//赋值 
	low[u]=times;//先赋初值 
	vis[u]=true;//vis[i]用来判断i是否搜索过;
	insta[u]=true;//表示是否在栈中,true为在栈中; 
	stack[top]=u;//栈顶 
	top++;
	for(int i=head[u];i!=-1;i=edge[i].next)// 以建图顺序枚举此点所连的边 
	{
		int v=edge[i].to;//搜索到的点 
		if(!vis[v])//如果未搜索过即未入栈 
		{
			dfs(v);//继续以此点进行深搜 
			low[u]=min(low[u],low[v]);//更新low值,此边为树枝边所以比较u此时的
		}							// low值(未更新时就是其dfn值)和v的low值
		else 
			if(insta[v]==true)//如果搜索过且在栈中,说明此边为后向边或栈中横叉边
			{
				low[u]=min(low[u],dfn[v]);//更新low值,比较u此时的low值和v的dfn值 
			}
	}
	
	if(low[u]==dfn[u])//相等说明找到一个强连通分量 
	{
		while(top>0&&stack[top]!=u)//开始退栈一直退到 u为止 
		{
			top--;
			insta[stack[top]]=false;
		}
	}
}


模板例题:受欢迎的牛(popular cow)POJ2186,BZOJ1051;
链接:http://blog.csdn.net/qianguch/article/details/75309362

你可能感兴趣的:(图论问题,算法讲解)