【坚持不能偷懒】
譬如一个有向图,V个点,E条边
如果一个区域中的每个点都能到达任意另一个点
则称这个区域强连通
【相对朴素的Kosaraju算法】
需要对图Dfs两发
第一遍Dfs在dfs时记录一个时间(下面栗子里,括号里为时间)
譬如A->B->C B->D这样的图
我搜索A(1) ->搜索B(2) ->搜索C(3)
这时候,发现没了,退出 所以C变成(3,4) 4是退出时间
回到B(2) 继续搜索 -> D(5) ->没了-> D(5,6)
这时候回到B,发现B也没了,那么B(2,7)
回到A(1)
A也没了,变成A(1,8) 结束
所以用一个count记录时间,且Point(in,out)中,只有out是用到的,所以开一个int记录就行
第二遍Dfs
1、把所有边的方向反过来
2、根据 Point(in,out) 中的out最大的点 来dfs,凡是搜索到的,都是小伙伴
3、接着按照剩下out最大的点搜索小伙伴,重复
引用丹神blog的说法,顺毛刷一遍,逆毛刷一遍
复杂度其实也不是很高,两次Dfs
可以认为是 O( 2(M+V) )
【Tarjan算法】
只要dfs一发就行
【Gabow算法】
Tarjan的优化
引用丹神博客上的图
http://blog.csdn.NET/Danliwoo/article/details/51839290
————————PS————————
7 8 1 2 2 3 3 4 4 5 5 6 6 2 5 7 7 1
我以n=7 m=8 对丹神blog的tarjan测试
答案是正确的——一个连通块
但是 序号5的点(low[4])是1 而其余的是 0
(所以,如果要判断每个点的归属的话
可能需要加入并查集来确定?譬如引入Dfs它的来源点作为父节点?)——在退栈时更新Low就行了……
——————————————————
每个点维护两个值(Dfs[i] 和 Low[i])
Dfs[i]用于记录,Dfs到i的时间 用一个count来递增
(而Low[i]表示这个点所能到达的 最低的 Dfs[i]
如果能够维护这两个值,明显同一个连通分量的点
必然他们的Low值,是他们中最小的那个)——这句话好像不是那么准,譬如上面的反例
(用Low[i]维护它是否可达其他早于它的点,因为【维护的栈中】早的点必然可以到达晚的点
能够互相到达,表示连通形成)——好像这样解释更稳点
那怎么维护呢
Dfs,把Dfs到的点【入栈】,同时每个点在Dfs的回溯时更新Low
当Dfs的回溯中,遇到一个【未被改变的点】时
就把栈中的点(到它为止)退栈
同时,可以在退栈时,更新退出的点的low,这样,就能确保连通块的low一致了
把丹神的那个,退栈时加一个更新low的操作,测试下来没问题了。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
int n, m, ans;
#define N 10100
std::vector<int> v[N];
int vis[N], dfn[N], low[N], stk[N], top, ins[N], clk;
void Tarjon(int x){
dfn[x] = low[x] = clk++;
stk[top++] = x;
vis[x] = ins[x] = 1;
for(int j = 0;j < v[x].size();j++){
int y = v[x][j];
if(!vis[y]){
Tarjon(y);
low[x] = min(low[x], low[y]);
} else if(ins[y]){
low[x] = min(low[x], dfn[y]);
}
}
if(low[x] == dfn[x]){
int sz = 0;
do {
ins[stk[top-1]] = 0;
low[stk[top-1]] = low[x];
top--;
sz++;
} while(stk[top] != x);
if(sz > 1) ans++;
}
}
int main()
{
while(~scanf("%d%d", &n, &m)){
memset(v, 0, sizeof(v));
for(int i = 0;i < m;i++){
int x, y;scanf("%d%d", &x, &y);
x--; y--;
v[x].push_back(y);
}
memset(vis, 0, sizeof(vis));
memset(ins, 0, sizeof(ins));
top = ans = clk = 0;
clk = 1;
for(int i = 0;i < n;i++) if(!vis[i])
Tarjon(i);
for(int i = 0;i<n;i++){
cout<<i<<" "<<dfn[i]<<" "<<low[i]<<endl;
}
printf("%d\n", ans);
}
return 0;
}
/*
7 8
1 2
2 3
3 4
4 5
5 6
6 2
5 7
7 1
*/
我写的玩意儿……本着加深自己理解,别人看不看得懂看天意的原则……
所以推荐些更诚恳的讲解——
强连通分支算法--KOSARAJU算法、TARJAN算法和GABOW算法
http://www.cnblogs.com/luweiseu/archive/2012/07/14/2591370.html
(这个Blog上讲的细且好……)