图论算法-Tarjan之强连通分量

3 篇文章 0 订阅
2 篇文章 0 订阅

有向图的强连通分量

在明白强连通分量之前,先要明白 强连通强连通图 是什么

强连通

在图G中,互相连通的(U, V)两个点就叫它们两个点强连通

比如说

A
C
B

在此图中,A能到达C, C也能到达A,所以(A, C)强连通;B能到达A,但A不能到达B,所以(A,B)不是强连通

强连通图

在图G中,每个节点都能直接或间接连到其他的N-1个点,那么这个图就叫做强连通图

像下面这个图

A
B
C

在此图中,(A,B,C)都能连到其他任意一点
这时候,就会有聪明的小朋友说了:那B也没有线连到A呀!
注意,每个点都不一定直接连到其他点,可以间接地连到。比如这里的B,可以通过B–>C–>A来间接连到,所以,我们也叫(A,B)是可连通的

强连通分量

在图G中,有一个强连通子图g,子图g就是强连通分量

在下面这个图中

A
b
C
D

点(A,B,C)组成的一个子图就是一个强连通分量
注意:一个节点也可以形成一个强连通分量

Tarjan求强连通分量

Tarjan算法运用了DFS(深度优先搜索)与的技术来求「强连通分量」
通常,我们使用 「 d f n 」 「dfn」 dfn 「 l o w 」 「low」 low两个数组来实现Tarjan算法

  • d f n dfn dfn数组: d f n dfn dfn数组记录了搜索树的搜索时间戳(人话:深搜的搜索顺序),也记录了某个节点是否被遍历过
  • l o w low low数组: l o w low low数组记录了当前节点能访问到的当前搜索树中(栈中) 最早能访问到的节点编号,初始值与 d f n dfn dfn数组相同(搜索时间戳)
  • 如果 d f n dfn dfn l o w low low数组的值相同,那么就表示存在一个强连通分量

上面的几句话看不懂也没有关系(其实作者初学时也不明白),下面让我们模拟一下tarjan的全过程
这里使用 D F N / L O W DFN/LOW DFN/LOW来表示各节点

首先遍历到节点 1 1 1

更新节点1的 d f n dfn dfn l o w low low,使用 t i m e r timer timer变量来记录时间戳

d f n [ 1 ] = l o w [ 1 ] = + + t i m e r dfn[1] = low[1] = ++timer dfn[1]=low[1]=++timer

将节点1的编号入栈,并标记它 s t a c k . p u s h ( 1 ) , i n S t a c k [ 1 ] = t r u e stack.push(1), inStack[1] = true stack.push(1),inStack[1]=true

节点1被更新为 1 / 1 1/1 1/1
stack(栈): 1 1 1


然后遍历到节点 3 3 3,更新节点3为 2 / 2 2/2 2/2
d f n [ 3 ] = l o w [ 3 ] = + + t i m e r ; s t a c k . p u s h ( 3 ) , i n S t a c k [ 3 ] = t r u e dfn[3] = low[3] = ++timer; stack.push(3), inStack[3] = true dfn[3]=low[3]=++timer;stack.push(3),inStack[3]=true
stack(栈): 1 , 3 1,3 1,3


更新节点5为 3 / 3 3/3 3/3
d f n [ 5 ] = l o w [ 5 ] = + + t i m e r ; s t a c k . p u s h ( 5 ) , i n S t a c k [ 5 ] = t r u e dfn[5] = low[5] = ++timer; stack.push(5), inStack[5] = true dfn[5]=low[5]=++timer;stack.push(5),inStack[5]=true
stack(栈): 1 , 3 , 5 1,3,5 1,3,5


更新节点6为 4 / 4 4/4 4/4
d f n [ 6 ] = l o w [ 6 ] = + + t i m e r ; s t a c k . p u s h ( 6 ) , i n S t a c k [ 6 ] = t r u e dfn[6] = low[6] = ++timer; stack.push(6), inStack[6] = true dfn[6]=low[6]=++timer;stack.push(6),inStack[6]=true
stack(栈): 1 , 3 , 5 , 6 1,3,5,6 1,3,5,6
节点6往后遍历,发现没有了
发现节点6的 d f n [ 6 ] = l o w [ 6 ] dfn[6] = low[6] dfn[6]=low[6] : 6 / 6 6/6 6/6
那么栈中节点6节点6入栈后入栈的节点形成一个强连通分量
将它们记录,并将它们出栈
所以,stack更新为 : 1 , 3 , 5 :1,3,5 :1,3,5
{ 6 6 6}为一个强连通分量


往前回溯到节点 5 5 5
l o w [ 6 ] > l o w [ 5 ] low[6] > low[5] low[6]>low[5],所以不更新节点5
节点 5 5 5没有其他可到达的点了
并且节点 5 5 5 d f n dfn dfn l o w low low相同: 5 / 5 5/5 5/5
5 5 5 5 5 5以后入栈的节点出栈并记录
更新stack : 1 , 3 :1,3 :1,3
{ 5 5 5}为强连通分量


回溯到节点 3 3 3 l o w [ 5 ] > l o w [ 3 ] low[5] > low[3] low[5]>low[3],所以不更新节点3
继续遍历到节点 4 4 4


更新节点4为 5 / 5 5/5 5/5
d f n [ 4 ] = l o w [ 4 ] = + + t i m e r ; s t a c k . p u s h ( 4 ) , i n S t a c k [ 4 ] = t r u e dfn[4] = low[4] = ++timer; stack.push(4), inStack[4] = true dfn[4]=low[4]=++timer;stack.push(4),inStack[4]=true
stack(栈): 1 , 3 , 4 1,3,4 1,3,4
继续遍历,连接到了节点 1 1 1
节点 1 1 1还在栈内(此搜索树内),并且节点 1 1 1比节点 4 4 4遍历的时间戳小,所以**更新当前节点 4 4 4 l o w low low为节点 1 1 1 d f n dfn dfn 1 1 1
更新节点 4 4 4 5 / 1 5/1 5/1


再次回溯至节点 3 3 3
l o w [ 4 ] < l o w [ 3 ] low[4] < low[3] low[4]<low[3],更新节点3的 l o w low low为节点4的 l o w low low:1
当前节点3为: 2 / 1 2/1 2/1


继续回溯至节点 1 1 1
l o w [ 3 ] = = l o w [ 1 ] low[3] == low[1] low[3]==low[1],所以不更新节点 1 1 1


继续遍历至节点 2 2 2
更新节点 2 2 2为 : 6 / 6 6/6 6/6
d f n [ 2 ] = l o w [ 2 ] = + + t i m e r ; s t a c k . p u s h ( 2 ) , i n S t a c k [ 2 ] = t r u e dfn[2] = low[2] = ++timer; stack.push(2), inStack[2] = true dfn[2]=low[2]=++timer;stack.push(2),inStack[2]=true
stack(栈): 1 , 3 , 4 , 2 1,3,4,2 1,3,4,2
继续遍历,连接到了节点 4 4 4
节点 4 4 4还在栈内,并且节点 4 4 4比节点 2 2 2遍历的时间戳小,所以更新当前节点 2 2 2 l o w low low为节点 4 4 4 d f n dfn dfn 5 5 5
当前节点 4 4 4 6 / 5 6/5 6/5


回溯至节点1
节点1没有其他节点可遍历了
并且节点 1 1 1 d f n dfn dfn l o w low low相同: 1 / 1 1/1 1/1
1 1 1 1 1 1以后入栈的节点出栈并记录
更新stack : ( 空 ) :(空) :
{ 1 , 3 , 4 , 2 1,3,4,2 1,3,4,2}为强连通分量
在这里插入图片描述


至此,这张图就遍历完成了

先把tarjan的模板放上来

void tarjan(int u){
	dfn[u] = low[u] = ++timer;//初始化当前节点
	s.push(u), vis[u] = 1;//进栈,并标记
	for(int i = head[u]; i; i = e[i].nxt){//前向星存图
		int v = e[i].v;
		if(!dfn[v]){//如果目标节点没有遍历过
			tarjan(v);
			low[u] = min(low[u], low[v]);//更新当前节点
		}else if(vis[v]){//否则如果目标节点还在栈中
			low[u] = min(low[u], dfn[v]);//更新当前节点
 		}
	}
	if(low[u] == dfn[u]){//如果当前节点是强连通分量的根节点
		int v;
		ans++;//强连通分量个数加1
		do{
			v = s.top();
			s.pop();//出栈
			vis[v] = 0;//取消标记
			scc[v] = ans;//记录当前强连通分量的节点
		}while(u != v);
	}
}

例题

P1726 上白泽慧音

CODE:

#include <bits/stdc++.h>
using namespace std;
int n, m, k, cnt, head[5005], p;
int dfn[5005], low[5005];
int t[5005], ans, ak;
stack<int> s;
bool vis[5005];
struct edge{
    int v, nxt;
}e[1000005];
vector<int> g[5005];
void add(int u, int v){
    e[++cnt] = edge{v, head[u]};
    head[u] = cnt;
}
void tarjan(int u){
    vis[u] = 1, dfn[u] = low[u] = ++k;
    s.push(u);
    for(int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }else if(vis[v]){
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(low[u] == dfn[u]){
        p++;
        int v;
        do{
            v = s.top();
            g[p].push_back(v);
            vis[v] = 0;
            s.pop();
        }while(u != v);
    }
}
int main(){
    cin >> n >> m;
    for(int i = 1, u, v, w; i <= m; ++i){
        cin >> u >> v >> w;
        add(u, v);
        if(w == 2) add(v, u);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i]) k = 0, tarjan(i);
    }
    for(int i = 1; i <= p; ++i){
        if(g[i].size() > ans){
            ans = g[i].size();
            ak = i;
        }
    }
    cout << ans << endl;
    int len = g[ak].size();
    priority_queue<int, vector<int>, greater<int> > q;
    for(int i = 0; i < len; ++i) q.push(g[ak][i]);
    while(q.size()){
        cout << q.top() << ' ';
        q.pop();
    }
    return 0;
}

完结撒花!!!
如果您认为博主写的好的话,记得点赞+收藏哦!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值