kosaraju算法

这个算法用来寻找一个图的强连通子图,分为两步:

1、找到该图的反向图的一个拓扑排序

2、以该排序为顺序,深度优先遍历原图的每个节点,每个未遍历过的节点的dfs树都是一个scc(strongly connected component)。

这个算法从直观上是很简单的,如下图:


这就是一个图,其节点从左到右已经按拓扑排序,当然这个排序不是真的拓扑排序。现在先忽略两个红色的边,那这个图就是一个按严格拓扑排序的。

对于严格的拓扑,如果以从右往左的顺序dfs遍历,每一次遍历得到一个强连通子图,即一个节点,这不用证明也能看出来。

但如果这样的拓扑加入了反向的边(红色的)的话,我们惊讶地发现每次遍历得到的,咳,还是强连通子图。dfs(4)得到4,3,7的scc,dfs(2)得到2,1,0的scc。

这只是直观上的感受,试着想一下,没有反向边时从左往右是拓扑有序的,现在加入一条反向边,比如2→1,那么我们认为一定有一条正向边可以从1到达2,达成一个强连通图(其实就是个回路),如果没有的话,1→2就没有拓扑的偏序关系,那么2可以放在1左边,该反向边就不是一条反向边,矛盾,因此按反向图的拓扑对正向图dfs就能得到连通子图。注意这不是严格的证明。

一个图的拓扑可以用dfs的时间戳得到,具体用的时候就是dfs的时候用了个栈把被递归的节点存下来,这样可以求出反向图的拓扑,用这个顺序来dfs正向图即可,每次有未遍历的节点都代表一个新的子图。简单的实现一下:

#include<iostream>
#include<stack>
#include<cstring>
#include<vector>
using namespace  std;
const int N=6;
int scc[N],vis[N],sec=0;
vector<int> adj[N],radj[N];
vector<int> topo;

void init()
{
    for(int i=0;i<N;++i)
    for(int j=0;j<N;++j)
    {
        int tmp;    cin>>tmp;
        if(tmp)  {
            adj[i].push_back(j);
            radj[j].push_back(i);}
    }
}
void rdfs(int k)
{
    vis[k]=1;
    for(int i=0;i<radj[k].size();++i)
        if(!vis[radj[k][i]])     rdfs(radj[k][i]);
    topo.push_back(k);
}

void dfs(int k)
{
    vis[k]=1;   scc[k]=sec;
    for(int i=0;i<adj[k].size();++i)
        if(!vis[adj[k][i]])     dfs(adj[k][i]);
}

void kosaraju()
{
    init();
    memset(vis,0,sizeof(vis));
    for(int i=0;i<N;++i)
        if(!vis[i])     rdfs(i);

    memset(vis,0,sizeof(vis));
    for(int t,i=topo.size()-1;i>=0;--i)
        if(!vis[t=topo[i]])
            { ++sec; dfs(t) ;  }
    for(int i=0;i<N;++i)cout<<scc[i]<<" ";
}

int main(){ kosaraju();  return 0;}

输入6个点的图的边(0表示不连通,非零连通),输出为每个点所属的强连通图的标号。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值