tarjan强连通

tarjan

用于求强连通分量

细节内容

  1. low[] 数组改小是为了避免提前满足自己是自己的父节点而输出连通分量(因为一个强连通分量中,它的叶节点自身就是一个“强连通分量”,而真正的强连通分量是极大的)
  2. 程序中两次更新low,有环时改小不需要比较大小,一定是先访问的比较小
  3. 有环时改小先于回溯时改小,因为有环时改小了,才能在回溯的过程中,通过比较改小之前访问过的节点。
  4. 栈用来记录已经被访问过的值,
    Dfn 标记是否访问过,避免重复访问
    S 标记访问过,但是还在寻找整个强连通分量的点,避免提前输出不完整的强连通分量,当找到整个强连通分量的起始点时,沿着这个顶点输出所有的该强连通分量内的值,并弹栈,则对于这一强连通块的访问彻底结束。

模板

中间各种注释为代码解释
注释中的输出是递归树

输入样例
/*
9 13
1 2
1 7
2 3
2 5
2 9
3 4
4 2
5 6
5 7
5 8
6 5
8 6
9 1
*/

#include<iostream>
using namespace std;
int A[100][100]; // 邻接点
int Dfn[100], num; // 记录邻接点的访问次序
int Low[100]; // 动态改小点的访问次序
int Stack[100], top; // 顶点入栈,强连通分量出栈 
bool out[100]; // 出栈标记

void DFS(int u) {
	int v;
	Dfn[u] = Low[u] = ++num; // 记录u的访问次序 
	Stack[++top] = u; // 入栈 
//	printf("调用DFS(%d) D[%d]=L[%d]=%d ""S[%d]=%d\n", u, u, u, num, top, u); // 输出查看调用过程
	
	for(int i = 1; i <= A[u][0]; i++) {
		v = A[u][i]; // 取出u的邻接点 
		if(Dfn[v] == 0) { // 若没访问过 
			DFS(v);
			Low[u] = min(Low[u], Low[v]); // 回溯到点u时,改小Low[u],可能有环出现,下面子节点的Low[v]已经更小了
//			printf("回溯 %d->%d L[%d]=%d\n", v, u, u, Low[u]); // 跟踪输出 
		}// v 已访问且在栈中,说明u,v已构成环 
		else if(!out[v]){ // 由于栈中压在下面的元素不能立刻弹出,所以用out来标记弹出栈 
			Low[u] = Low[v]; // 有环时改小low,由于时从u访问到v,且v已经被访问过了,说明v先出现,次序肯定比u小,直接更新,不用比较 
//			printf("有环 %d->%d L[%d]=%d\n", u, v, u, Low[u]); 
		}
	} 
//	printf("判断 if(%d==%d)?\n", Dfn[u], Low[v]); // 跟踪
	// 向上层回溯前,判断根
	if(Dfn[u] == Low[u]) { // u是当前分量的根 
		printf("分量 ");
		while(Stack[top]!=u){  
			printf("%d ", Stack[top]); // 输出强连通分量成员 
			out[Stack[top--]] = 1;
		} // u之后访问的点均出栈
		printf("%d ", u); // u出栈
		out[Stack[top--]] = 1; // 出栈标记
//		printf("栈顶 S[%d]=%d\n\n", top, Stack[top]); //跟踪 
	}
} 

int main() {
	int u, v, n, m; // 点数, 边数
	scanf("%d%d", &n, &m); 
	for(int i = 0; i < m; i++) {
		scanf("%d%d", &u, &v);
		A[u][++A[u][0]] = v; // 邻接点 ,(邻接表存图)
	} 
	for(int i = 1; i <= n; i++) { // 图可能为森林,所以要从每个都都出发去找才能把所有强连通分量找全 
		if(Dfn[i] == 0) DFS(i);
	} 
	return 0;
} 

输出结果
在这里插入图片描述

递归树过程

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值