您的位置:首页 > 其它

【DayDayUp】【算法_图_强连通_之一_Kosaraju算法和Tarjan算法】

2017-03-16 10:57 134 查看
【坚持不能偷懒】

譬如一个有向图,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
;
int vis
, dfn
, low
, stk
, top, ins
, 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上讲的细且好……)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: