有向图强连通分量(核心代码图解分析)

理解有向图的强连通分量

每两个顶点都至少有一条路径(A->B,B->A),然后称这两个顶点具有强联通性,这两个顶点组成的图为强连通图。
在这里插入图片描述
从这个途中看到,阴影部分是强连通图,拿6看,6与7不是强连通图的原因是7到6可以,但是6到7找不到一条路径。

Tarjan算法

对tarjan对计算强连通分块计算理解:
对核心部分(伪代码)的下图理解

for i in (u,v){  //u指向v,下面都说成相邻了
	if v 未被访问:
		 dfs(v)
		 low[u] = min(low[u],low[v]);   //跳出来之后刷新最值
	else if v 被访问过:
		 low[u] = min(low[u],dfn[v]);
}

下面就是对上面这几行代码的图示

首先给传入了节点1,遍历1附近的点,先遍历到了2,3未被访问,进栈;
然后遍历3附近的点,先遍历到了5,5未被访问,进栈;
然后遍历5附近的点,先遍历到了6,6未被访问,进栈;
(此时是初始化了这几个节点的low,但是由于回溯的时候还会更新,所有此时并未写出)
然后遍历6附近没有(就没有进6附近遍历的循环),而是直接进入下面的判断该节点是否为强连通分量,这个节点dfn == low,所以6为强连通分量
此时num++,变成1,size[1]记录了6这个连通分量的大小
在这里插入图片描述


然后退到让一层,此时刷新节点5的low值,经过比较,low[5] = 3,low[to[i]] = 4,所以还是本身的3。然后这一层的for遍历的邻近的节点只有6,遍历完了,就跳出了,也进入了判断该节点是否为强连通分量,这个节点dfn == low,所以5为强连通分量。
此时num++,变成2,size[2]记录了5的强连通分量

在这里插入图片描述


然后5遍历完了之后再往回回溯,到3(这里还有一个刷新当前的low的值的过程,low[3] = 2,low[to[i]] = 3,所以不变),3那层的for循环遍历的邻近节点还有一个4,发现4为未被访问,
然后访问4,然后遍历4的所有邻近的节点,发现1和6都是已经被访问过的了
被访问就代表已在栈中,然后刷新low值比较的是当前这层的low[4] = 5 和dfnt[to[i]](也就是节点1和节点6的dfn进行比较取小的),最后刷新出节点4的low[] = 1;
然后回溯,3的low被4的low刷新为1,然后返回节点1。
节点1那一层遍历的邻近节点还有2未被访问。
在这里插入图片描述


访问2,然后遍历发现4被访问,然后比较2节点的low[2] = 6与dfnt[to[i]](也就是节点4的dfn = 5比较),最后刷新low[2] = 5。
然后回溯到1,1的low也被比较了一次,但并未刷新,
最后节点1跳出那一层的循环,然后判断该节点是否为强连通分量。
num++,变为3,size[3]储存是1,2,3,4这个强连通分量。
在这里插入图片描述
准备:

  • 一个数组来模拟栈:用来暂时储存节点,然后通过对条件的判断是否进出栈,最后留在栈中的是一个强联通分量的图的所有节点,所以有最后退栈的操作,即看这个连通图的组成部分。
  • 储存图中关系的方法(spfa 网络流费用流有时也会用到这种储存方法):
    • 有向图(有方向性):head[maxn] ,nt[maxn], to[maxn (后面这两个数组要开大一点,防止爆掉)
    • 在储存数据的时候将指向关系通过数组存起来
void addedge(int a,int b){
	nt[++cnt] = head[a];
	head[a] = nt[cnt];
	to[cnt] = b;
}
  • 遍历一遍这个点连接的其他所有点

  • 传进来了一个节点,然后遍历这个节点相邻的其他所有点

//u就是传进来的那个点
for(i = head[u];i;i =nt[i]){
	//to[i]是拿到的那个相邻的点
}
  • 用num来记录强连通分块的个数,用size[]来储存所有的强连通分块的元素的个数

代码:

//
//  main.cpp
//  强连通分量_模版_cow
//
//  Created by 陈冉飞 on 2019/8/14.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
int n,m,a,b,ch,ans;  //注意声明i的时候别声明全局
#define maxn 10005
int head[maxn],nt[maxn*5],to[maxn*5],cnt;    //记录点的坐标
int in[maxn],stac[maxn],top,num,size[maxn];    //开一个栈来储存当前的点,top是顶上的点
int dfn[maxn],low[maxn],tim;
int chu[maxn];


void addedge(int a,int b){
    nt[++cnt] = head[a];
    head[a] = cnt;
    to[cnt] = b;
}

void dfs(int k){
    stac[++top] = k;
    dfn[k] = low[k] = ++tim;
    //往后一直延伸     访问这个节点所有相连的节点
    for (int i = head[k]; i; i = nt[i]) {
//        cout<<i<<endl;
        if(!dfn[to[i]]){   //如果没有访问
            dfs(to[i]);   //访问,然后回溯回去
            low[k] = min(low[k],low[to[i]]);
        }else if(!in[to[i]]) low[k] = min(low[k],dfn[to[i]]);   //被访问了,回溯
    }
    if (low[k] == dfn[k]) {   //子树为强连通分量,就要一直退栈
        ++num;
        while(stac[top] != k){
            in[stac[top--]] = num;
            ++size[num];
        }
        in[stac[top--]] = num;
        size[num]++;
    }
}

int main(int argc, const char * argv[]) {
    cnt = 0;
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d",&a,&b);
        addedge(a,b);
    }
    //检验输出
//    for (int i = 1; i <= n; i++) {
//        cout<<i<<"   "<<nt[i]<<"   "<<head[i]<<"   "<<to[i]<<endl;
//    }
    for (int i = 1; i <= n; i++)
        if (!in[i]) dfs(i);
//    cout<<"强连通块的个数"<<num<<endl;
//    for (int i = 1; i <= num; i++) cout<<i<<"  "<<size[i]<<endl;
    for (int i = 1; i <= n; i++)
        for (int j = head[i]; j; j = nt[j])
            if (in[i] != in[to[j]]) chu[in[i]]++;
    for (int i= 1; i <= num; i++) {
        if (chu[i]) continue;
        if (ch++) {cout<<"0"<<endl;return 0;}
        ans = size[i];
    }
    cout<<ans<<endl;
    return 0;
}

注意这种dfs递归的i这种循环里经常用到的索引就不要声明为全局,哪一层改i就会引起其他层的i变化,所以不如多费点空间,让每一层都有个对应的i,防止死递归。(一开始声明全局i,看了将近一个小时愣是没看出来)

参考博客:
算法之《图》Java实现
有向图强连通分量的Tarjan算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值