1 karosaju算法的理解:
对其每一步的理解详情见: https://www.cnblogs.com/nullzx/p/6437926.html
2 karosaju算法的实现:
此算法主要针对有向图的强连通子集的查询;
- 对原图进行dfs(当然有可能是多次,直到你遍历所有的点),按照结束时间从早到晚,将其压入finish的数组中;
- 对逆图: 按照1步里面的finish,从最晚结束的节点开始,进行dfs,每次dfs可以获得一个树,(因为该图可能不会一次性让你dfs完,所以你得按照finish里的顺序,从最晚结束的,到最早结束的,直到所有的点都被你dfs完)这样每一次dfs就可以获得一子图,这个子图就是一个强连通子图;
注: 结束时间,就是你dfs中压入以后,弹出的时刻,就叫做结束时间,获得finish的方法其实就是一旦有一个节点出栈,你就将该节点入栈即可;
3 karosaju算法实例:
例题是ccf里面的第四题:
试题编号: | 201509-4 |
试题名称: | 高速公路 |
时间限制: | 1.0s |
内存限制: | 256.0MB |
问题描述: | 问题描述 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。 输入格式 输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。 输出格式 输出一行,包含一个整数,表示便利城市对的数量。 样例输入 5 5 样例输出 3 样例说明
评测用例规模与约定 前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000; |
解决方案采用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
*/
|