karosaju算法求图中的强连通子集

28 篇文章 0 订阅

1 karosaju算法的理解:

对其每一步的理解详情见: https://www.cnblogs.com/nullzx/p/6437926.html

2 karosaju算法的实现:

此算法主要针对有向图的强连通子集的查询;

  1.  对原图进行dfs(当然有可能是多次,直到你遍历所有的点),按照结束时间从早到晚,将其压入finish的数组中;
  2.  对逆图: 按照1步里面的finish,从最晚结束的节点开始,进行dfs,每次dfs可以获得一个树,(因为该图可能不会一次性让你dfs完,所以你得按照finish里的顺序,从最晚结束的,到最早结束的,直到所有的点都被你dfs完)这样每一次dfs就可以获得一子图,这个子图就是一个强连通子图;

注: 结束时间,就是你dfs中压入以后,弹出的时刻,就叫做结束时间,获得finish的方法其实就是一旦有一个节点出栈,你就将该节点入栈即可;

3 karosaju算法实例:

例题是ccf里面的第四题:

试题编号:201509-4
试题名称:高速公路
时间限制:1.0s
内存限制:256.0MB
问题描述:

问题描述

  某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。

输入格式

  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b

输出格式

  输出一行,包含一个整数,表示便利城市对的数量。

样例输入

5 5
1 2
2 3
3 4
4 2
3 5

样例输出

3

样例说明


  城市间的连接如图所示。有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。

评测用例规模与约定

  前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
  前60%的评测用例满足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
  所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。

解决方案采用karosaju算法,该算法利于理解,图的存储采用邻接表,避免超出内存;

对于算法的进一步理解可以直接看代码,代码有详细的注释,且易于理解:

#include <iostream>
#include <vector>
#include <algorithm>
#include <math.h>
#include <limits.h>
#include <stdlib.h>
#include <string>
#include <map>
#include <stdio.h>

using namespace std;

int n, m;
int i, j;

typedef struct edge {
    int u = 0, v = 0, w = 1;
}edge;

#define MAX_N 10001
#define MAX_M 100001

//返回-1,说明都访问了,否则就是找到的第一个没有被访的点
//par1 : 哪些点被访问过
//par2 : 访问的顺序
int HelpDfs(vector<bool >& traveled, vector<int >& start){
    //找到在start中第一个没有被访问过的点
    for(int i=1; i<start.size(); i++){
        if(traveled[start[i]] == false) return start[i];
    }
    return -1;
}

//ret  : 函数的返回值是每个联通分量里含有顶点数目的大小
//par2 : finish记录的是从0到尾按照dfs访问的结束时间从早到晚的顶点
//par3 : 访问顺序
vector<int > Dfs(vector<int >* mat, vector<int >& finish, vector<int >& start){
    vector<bool >traveled(n+1, false);
    vector<int > travel(0);
    vector<int > cc(0);
    //从1开始记录结束的点
    finish.push_back(0);
    int st = start[1];
    //当前联通分量含有节点的个数
    int cnt = 0;
    //对于一个可能不能一次dfs就完成的,只能通过查看是否所有的点都被访问完了来做
    do{
        //以st开始dfs, 初始化遍历栈
        travel.push_back(st);
        traveled[st] = true;
        while(travel.size()!=0){
            //找栈顶的邻居还没被访问过的,入栈
            int top = travel[travel.size()-1];
            bool top_done = true; //top的邻接点都被访问了,需要出栈
            for(int i=0; i<mat[top].size(); i++){
                if(!traveled[mat[top][i]]){
                    travel.push_back(mat[top][i]);
                    traveled[mat[top][i]] = true;
                    top_done = false;
                    break;
                }
            }
            //弹出节点,并且放入完成的栈,做上访问的标记
            if(top_done){
                finish.push_back(top);
                traveled[top] = true;
                travel.pop_back();
                cnt++;
            }
        }
        cc.push_back(cnt);
        cnt = 0;
    }while(-1 != (st = HelpDfs(traveled, start)));
    return cc;
}

int main()
{
    cin >> n >> m;
    vector<int > mat[MAX_N];
    vector<int > neg_mat[MAX_N];
    vector<edge> edges(MAX_M);

    //采用邻接表的方式存储
    for(i=1; i<m+1; i++){
        cin >> edges[i].u >> edges[i].v;
        mat[edges[i].u].push_back(edges[i].v);
        neg_mat[edges[i].v].push_back(edges[i].u);
    }

    //    print2V(mat, 5);
    //正向遍历图,获得栈顶是最后完成的点的栈
    vector<int > vis_seq(0); //在第一次Dfs获得访问逆图的访问顺序
    vector<int > init_seq(n+1); //在第1次dfs中,对顺序没有要求的,那么就是从小到大;
    for(int i=1; i<n+1; i++) init_seq[i] = i;
    Dfs(mat, vis_seq, init_seq);

    //遍历其逆图,每一个分图就意味着一个强连通图
    //这里错就错在你没有把整个finish搞进去让他们遍历
    vector<int > no_use(0);
    reverse(vis_seq.begin()+1, vis_seq.end());
    vector<int > cc = Dfs(neg_mat, no_use, vis_seq);

    //计算pairs之和
    int pairs = 0;
    for(int i=0; i<cc.size(); i++){
        pairs += (cc[i]*(cc[i]-1))/2;
    }

    cout << pairs << endl;

    return 0;
}


/**
5 5
1 2
2 3
3 4
4 1
5 4

6 7
1 2
2 3
3 1
3 4
4 5
5 6
6 4

*/

 

 

 

 

 

 

 

 

 

  
  
  
  
 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值