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]=++index;
    low[u]=dfn[u];
    stack[++top]=u;
    vis[u]=1;
    for(int i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(!dfn[v]){
            tarjan(v, u);
            low[u]=MIN(low[u], low[v]);
        }else if(vis[v])
            low[u]=MIN(low[u], low[v]);
    }
    if(dfn[u]==low[u]){
        int temp;
        do{
            temp=stack[top];
            vis[temp]=0;
            cout<<temp<<" "; 
            top--;
        }while(temp!=u);
        cout<<endl;
    }
}

注意在本代码中 index要初始化为-1完整程序

割点

原理

思路同上,如果一个节点不为根节点且只要有一个子节点通过返祖边不能到达这个节点的祖先节点,那么这个节点就一定是一个割点。而如果这个节点是个根节点的话,只要其子树数大于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遍历的性质,异曲同工之妙)

扫描二维码关注公众号,回复: 2947416 查看本文章

伪代码

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

猜你喜欢

转载自www.cnblogs.com/santiego/p/9556474.html
今日推荐