强连通分量 Kosaraju PK Tarjan(转)

转自scameeling的空间

http://hi.baidu.com/scameeling/item/b135831094ec756771d5e815

 

强连通分量 Kosaraju PK Tarjan

Kosaraju算法

对每个不在树中的点开始DFS一次,并记录离开各点的时间,这里是离开的时间,而不是到达时的,比如有图1->2 2->3 则1,2,3分别对应的时间是3 2 1,因为3没有出边,所以最先离开,其次是2,最后是1,

DFS后,在同一棵树中的点,如果dfn[v]>dfn[u]则说明点从v有可能到达u,而这棵树中的dfn[]最大的点,肯定可以到达每个点,从而在原图的逆图中,每次都选没有访问过的最大的dfn值开始DFS,如果可达点x 则说明它们是强连通的

void DFS_T(int u)
{
    int i,v;
    if(used[u])return ;
    used[u]=1;id[u]=scc;
    for(i=q[u];i!=-1;i=Tedge[i].pre)
    {
        v=Tedge[i].d;
        if(!used[v])DFS_T(v);
    }
}
void DFS(int v){
    int i,u;
    if(used[v])return ;
    used[v]=1;
    for(i=p[v];i!=-1;i=edge[i].pre)
    {
        u=edge[i].d;
        if(!used[u])DFS(u);
    }
    order[++num]=v;
}
int Kosaraju()
{
    int i,j,k,v,u;
    memset(used,0,sizeof(used));num=0;
    for(i=1;i<=n;++i)if(!used[i])DFS(i);
    memset(used,0,sizeof(used));
    memset(id,0,sizeof(id));scc=0;
    for(i=num;i>=1;--i)if(!used[order[i]])scc++,DFS_T(order[i]);

}

 

Tarjan算法

dfn[v] 记录到达点v的时间,跟上面的离开不同,low[v]表示通过它的子结点可以到达的所有点中时间最小值,即 low[i]=min(low[i],low[u]),u为v的了孙,初始化时low[v]=dfn[u]。如果low[v]比dfn[v]小,说明v可 以通过它的子结点u,u1,u2...到达它的祖先v',则存在环,这个环上所有的点组成的子图便是一个强连通分量。换一个角度看,如果当 low[v]==dfn[v]时,则它的子树中所有low[u]==dfn[v]的点都与v构成一个环,维护一个栈,DFS过程中,每遍历一个点则把它放 入栈中,当发现low[v]==dfn[v]则依次把栈里的元素都弹出来,当栈顶元素为v时结束,这些点便构成一个以v为树根的强连通分量。

仍以上图为例,首先遍历点1,并dfn[1]=low[1]=++num, num表示按先后访问时间编号 ,同时1入栈

a.从3深入 dfn[3]=low[3]=2; 3入栈

b.从3到5 dfn[5]=low[5]=3; 5入栈

c.从5到6 dfn[6]=low[6]=4; 6入栈

d.发现6没有子结点可走,这时判断dfn[6]==low[6],于是开始弹栈,当遇到6时则break,即共弹出一个元素,于是6便是一个强连通分量

e.回溯至5,同样判断和弹栈,发现5也是一个强连通分量

f.再回溯至3,发现有边3->4,dfn[4]=low[4]=5,4入栈

g.4有边到1,由于1已经在栈里面,所以用dfn[1]更新low[4] 即low[4]=min(low[4],dfn[1])=1

h.回溯更新4的父亲3的low值 low[3]=min(low[3],low[4])=1

i.再回溯至1,发现有边1->2 继续深度遍历,2入栈,发现它的子结点4已经在栈中,直接更新low[2]=min(low[2],dfn[4]);

j.回溯至1,从而1所有出发的边都走了一遍,这时再比较low[1]与dfn[1],发现相等,于是开始弹栈,找到2,4,3,1这四个元素,构成一个连通分量。

void Tarjan(int v){
    dfn[v]=low[v]=++num;
    used[v]=1;
    st[++numSt]=v;
    for(int i=p[v];i!=-1;i=edge[i].pre){
        int u(edge[i].d);
        if(!dfn[u])//还没有标号的点
        {
            Tarjan(u);//先遍历它的子结点
            GetMin(low[v],low[u]);//用子结点更新当前点的low值
        }
        else if(used[u]&&GetMin(low[v],dfn[u]));
    }
    if(dfn[v]==low[v]){
        scc++;
        while(1){
            int u(st[numSt--]);
            id[u]=scc;
            used[u]=0;
            if(v==u)break;
        }
    }
}

int main(){

for(int i=1;i<=n;++i)if(!dfn[i])Tarjan(i);

}

 

转载于:https://www.cnblogs.com/Yu2012/archive/2012/07/30/2615189.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值