Tarjan算法打包总结(求强连通分量、割点和Tarjan-LCA)

Tarjan打包总结(求强连通分量、割点和Tarjan-LCA)

写给自己的Tarjan算法总结,包括求强连通分量、割点和Tarjan-LCA,基础概念就没有废话了,只写自己的理解和板子

强连通分量&缩点

原理

在DFS生成树中,如果一个节点通过其所有子节点的返祖边恰能达到这个节点,那么这些满足条件的点中最高的那个节点一定是强连通分量

伪代码

tarjan(u)
{
    DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
    Stack.push(u)                              // 将节点u压入栈中
    for each (u, v) in E                       // 枚举每一条边
        if (v is not visted)               // 如果节点v未被访问过
            tarjan(v)                  // 继续向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in S)                   // 如果节点v还在栈内
            Low[u] = min(Low[u], DFN[v])
    if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
        repeat
            v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
            print v
        until (u== v)
}

板子(C++)

void tarjan(int u){
    dfn[u]=++cnt;
    low[u]=cnt;
    ins[u]=1;
    s[++top]=u;
    for(register int i=head[u];i;i=nxt[i]){
        int v=vv[i];
        if(dfn[v]==0){
            tarjan(v);
            low[u]=MIN(low[u],low[v]);
        }else if(ins[v])
            low[u]=MIN(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        ins[u]=0;
        while(s[top]!=u){
            //do something
            ins[s[top]]=0;
            top--;
        }
        top--;
    }
}

割点

原理

思路同上,如果一个节点不为根节点且只要有一个子节点通过返祖边不能到达这个节点的祖先节点,那么这个节点就一定是一个割点。而如果这个节点是个根节点的话,只要其子树数大于1就一定是个割点了。

例如下图中,1和2就是割点

dfs生成树:

实边为树边,虚线为返祖边

伪代码

tarjan(u, fa){
	dfn[u]=low[u]=++index                      // 为节点u设定次序编号和Low初值
    int cnt=0;								 //统计子节点个数
    for each (u, v) in E                       // 枚举每一条边
        cnt++;
    	if (v is not visted)               // 如果节点v未被访问过
            tarjan(v);
            low[u]=min(low[u], low[v]);
            if(u is root and cnt>1)			//如果当前节点是根节点且其子树数大于1
                ans[u]=1;		//那么节点u就是一个割点
    		else if(low[v]>=dfn[u])			//如果有一个子节点v通过返祖边不能到达这个节点的祖先节点
                ans[u]=1;		//那么节点u就是一个割点
         else if(v!=fa)		//必须确保节点不为其父亲,否则会陷入死循环
             low[u]=min(low[u], low[v]);
}

最近公共祖先(LCA)

原理

因为DFS遍历的性质,所以任意一对询问第一次都已经被访问到时,此时在已经访问过的最低且度不为0的节点就是这次询问的公共祖先(其实另外一个doubly在线算法也是利用了DFS遍历的性质,异曲同工之妙)

伪代码

tarjan-lca(u)//marge和find为并查集合并函数和查找函数
{
    for each(u,v)    //访问所有u子节点v
    {
        Tarjan(v);        //继续往下遍历
        marge(u,v);    //合并v到u上
        标记v被访问过;
    }
    for each(u,e)    //访问所有和u有询问关系的e
    {
        如果e被访问过;
        u,e的最近公共祖先为find(e);
    }
}

板子

void tarjan-lca(int u, int fa){
    fa[u]=u;//并查集初始化
    for(register int i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(v==fa)	continue;
        Tarjan(v);
        fa[v]=u;//merge
    }
    for(register int i=0;i<q[u].size();i++){
        int v=q[u][i].data;
        if(fa[v]) ans[q[u][i].id]=find(v);
    }
}

本文采用 知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆 许可协议进行许可。欢迎转载,请注明出处: 转载自:Santiego的博客

posted @ 2018-08-29 19:43  Santiego  阅读(405)  评论(0编辑  收藏  举报