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

【坚持不能偷懒】

譬如一个有向图,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上讲的细且好……)



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值