【笔记】tarjian算法 求强连通分量

17 篇文章 1 订阅
4 篇文章 0 订阅

求解强连通分量

目的

求解强连通分量,缩环,利用节点的sccno编号重新建图,达到题目的要求或者将原图转为DAG,进行图上DPor最短路算法

算法流程

变量声明:
dfn:该节点的dfs编号
low:该节点及其后代能够追溯到的最早的祖先的dfn编号
1.到达新节点,更新dfn,将low初始化为本身的dfn序号,并将其压入栈中
2.遍历u的子节点v,
如果dfn[v]=0,即v还没有被访问过,那么< u,v >是一条树边,v是u的后代,对v进行tarjian操作,根据low数组的定义,v能到的的low,u也能到达,所以用low[v]更新low[u]
如果dfn[v]!=0&&sccno[v]!=0,说明v是u的祖先,且v不属于之前的强连通分量,那么用dfn[v]更新low[u]
3.如果dfn[u]==low[u]说明u节点是一个强连通分量的第一个节点,那么将栈内元素弹出,直至弹出元素为u,目的是将不同的scc区分开来。

细节理解

dfn[v]!=0&&sccno[v]!=0,v号节点必须不属于其他scc的原因
原理不会,但是我有实例啊
如图,u是下面的节点,v是上面的被指向的节点。显然v在一个scc中,u自己是一个scc
这里写图片描述

此时,如果用dfn[v]更新low[u],那么low[u]!=dfn[u],在之后的出栈操作中,u就不会被当做一个单独的强连通,这与图示不符。

模板

void tarjian(int u){
    dfn[u]=low[u]=++dfn_cnt;
    s[++top]=u;
    for(int i=fisrt[u];i!=-1;i=next[i]){
        int v=e[i].t;
        if(!dfn[v]){
            tarjian(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v]){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        ++scc_cnt;
        for(;;){
            int x=s[top];
            top--;
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
    return ;
}

粘一发black学长http://blog.csdn.net/loi_black的板子,black学长新开数组in_stack记录同一强连通分量,避免了对!sccno[v]的理解,感谢教练教我tarjian

void group(int x)
{
    dfn[x]=low[x]=++tot;    //累加遍历序号 
    stack[++snum]=x;    //把这个点压入栈内 
    in_stack[x]=1;      //表示这个元素在栈内存在 
    for(int i=first[x];i;i=next[i])
    {
        int u=hh[i].t;
        if(!dfn[u])     //这个点没有搜过,所以这条边是树边 
        {
            group(u);
            low[x]=min(low[x],low[u]);  //用low值更新low值 
        }
        else if(in_stack[u])    //搜过且在栈内,他们属于一个强连通分量,这条边是一条非树边 
            low[x]=min(low[x],dfn[u]);   //用dfn更新它的low值 
    }
    if(dfn[x]==low[x])  //x是这个强连通分量中在dfs时最先搜到的点 
    {
        cnt++;  
        while(true)
        {
            jlqlt[stack[snum]]=cnt;     //栈内的元素都是在一个强连通分量里面 
            size[cnt]++;    //这个是记录每个强连通分量的大小 
            in_stack[stack[snum]]=0;    //弹栈 
            snum--;
            if(stack[snum+1]==x)    //一直到弹到属于这个强连通分量中的元素全被弹干净 
                break;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值