Tarjan算法

来源:互联网 发布:数龙网络 编辑:程序博客网 时间:2024/05/21 06:13

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

这里写图片描述
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算法搜索后的一个有向图。由图可知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[x]=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[x]==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;

0 0
原创粉丝点击