强连通分量的Kosaraju算法

http://edward-mj.com/archives/455

dfs真是神奇,大师们利用简简单单的深搜搞出了不知道多少神奇的图论算法。。

图论问题就得往树的方向想,改天得好好做一些跟树相关的题目了。

我在扯什么啊。。。

/

算法的步骤是:

1:先利用拓扑排序的方法dfs原图,用一个栈保存所有的点,最后搜到的点先出栈(如果原图是DAG的话现在就是拓扑序了)

2:在反图(所有的边方向)中从栈顶元素开始dfs,每次能搜到的所有的点都会在一个极大连通分量中。

很简洁的方法,来证明一下吧。(虽然已经看过算法导论与上面链接了,不过还得自己好好理清楚写出来才是自己的)。

如果我们能证明该算法每次搜出的一个块都是强连通分量以及不存在把一个大的强连通分量拆成两个小的强连通分量的情况,我们就能证明这个算法的正确性了。

一:当前栈顶元素X与其搜到的所有的点Yi构成了一个强连通分量。

假设在反图的dfs中(也就是第二次dfs)从X搜到了Y,那么说明原图中有Y到X的路径。

既然X搜到了Y,说明,X比Y后出栈,只能有这样的解释:Y是X搜索树中的一点(原图中),也就是说原图中有X到Y的一点,再由上得X Y在一个强连通分量中。

其实X比Y后出栈理论上还有一种可能,就是Y 和 X是并行的两棵搜索树的根(原图中),Y先搜完了再去搜X的子树,但是由于已经知道了原图中有Y到X的路径,因此这种情况是不可能出现的。

二:不存在这样的两个点X、Y,他们能互相到达,但是分别位于两个不同的连通分量,也就是说上述算法搜出来的所有连通分量都是极大的强连通分量。

这个证明我感觉超简单,因为第二次dfs不管X与Y谁先出来搜,一个点肯定能搜到另外一个点,他们肯定就会在一个连通分量内了!

//

求出的强连通分量已经是拓扑序的了!

证明:待补

#include <vector>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define foreach(it,G) for(__typeof(G.begin())it = G.begin(); it != G.end(); it++)
const int N = 100010;
std::vector<int> G[N], rG[N], vs;
bool used[N];
int comp[N];
void add_edge(int a,int b) {
  G[a].push_back(b);
  rG[b].push_back(a);
}
void dfs(int u) {
  used[u] = true;
  foreach(it, G[u]) if(!used[*it]) dfs(*it);
  vs.push_back(u);
}
void rdfs(int u,int which) {
  used[u] = true;
  comp[u] = which;
  foreach(it,rG[u]) if(!used[*it]) rdfs(*it, which);
}
int scc(int n) {
  std::fill(used, used + n, false);
  vs.clear();
  for(int v = 0; v < n; v++) if(!used[v]) dfs(v);
  std::fill(used, used + n, false);
  int cnt = 0;
  for(int i = vs.size() - 1; i >= 0; i--)
    if(!used[vs[i]]) rdfs(vs[i],cnt++);
  return cnt;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值