C++图论之强连通图

1. 连通性

什么是连通性?

连通,字面而言,类似于自来水管道中的水流,如果水能从某一个地点畅通流到另一个地点,说明两点之间是连通的。也说明水管具有连通性,图中即如此。

无向图和有向图的连通概念稍有差异。

无向图连通性

如果任意两点间存在路径,称此图具有连通性,如下的图结构具有连通性。提及连通性,就不得不说连通分量,通俗而言,指结构中有多少个连通通道,如下的图结构只有一个连通通道,也就是一个连通分量,所有节点在这个连通通道上都能互通。

1.png

而下图,则有2个连通分量。1,2,3,4,5可以在一个连通通道上互通,不能和6,7互通。6,7在自己的连通通道上可以互通。

2.png

如何检查图结构的连通性和计算连通分量?

笨拙的方案自是使用深度或广度搜索算法。原理较简单,一次搜索完毕后,搜索到的节点必是在一个连通分量上。如果一次搜索完毕后被搜索出来的节点数量和图结构原有的节点数量相同,可证明只有一个连通分量。否则,可以再次从除第一次搜索出来的节点之外的节点开始重新搜索,再检查搜索出来的节点数量……如此如此,便可以检测出所有连通分量。

在性能要求不高的应用场景,这是不错的选择。否则,可以使用轻巧、快速的并查集数据结构来检查。

有向图的连通性

无论是在有向图或无向图中,都不可能改变连通这个概念。区别于有向图中的边有方向,无向图中的连通可以认为是双向通道,可认为是广义连通;有向图中的连通则是单向通道,可认为是狭义连通。

有向图中,如果一个节点能通过单向通道到达另一个节点,可认为这两点之间是连通的。如下图中,4->1、2->4->1是连通的,而2-3是不连通的。讨论连通的局部性没有太大意义,有向图中讨论的是强连通性。

3.png

什么强连通?

强连通是有向图的特定概念。有向图中,任意两点之间都可以连通,则认定此有向图为强连通图,如下图。

4.png

连通分量用来记录连通通道的数量,有向图中的连通分量指强连通分量。如上图,有一个强连通分量,也称此图为强连通性有向图。

如下图所示有向图结构,有向图本身不具有强连通性,但存在子图具有强连通性,则称子图即为原图的强连通分量。

5.png

当然,具有强连通性的子图可能不只一个。猜一猜,下图有几个连通分量。

6.png

我们已知在无向图中计算连通分量的算法。那么在有向图中如何计算机强连通分量?

算法界有一句名言:没有暴力算法不能解决的问题。有向图中查找强连通子量,同样可以使用深度搜索或广度搜索。可以说,在树和图论问题中没有广度和深度搜索算法解决不了的。说起来感觉很历害,道理却是简单,任何问题都是在能搜索到的前提下得到解决的。

直接使用广度或深度搜索,毫无疑问属于暴力算法。虽然这是一条康庄大道,但是,不一定是一条捷径之路。好吧,现在让我们去发现是否有捷径小道。

2. Tarjan算法

Tarjan算法很优秀,也很优雅,颇有风淡云轻,四两拨千金之感。理解Tarjan算法,先要知晓几个概念,如DFS序、时间戳、回溯值……这些可以查阅我的文章《DFS序和欧拉序的降维打击》。

Tarjan可以解决很多问题。如公共祖先、割点、割边……当然还有本文的强连通分量的求解。

理解Tarjan算法求解强连通分量的工作机制之前,先搞明白有向图的 DFS 生成树中的 4 种边。
12.png

  1. 树边(tree edge):节点与节点之间的边。
  2. 反祖边(back edge):上图中以红色边表示(即 7->1),也被叫做回边,即指向祖先节点的边。
  3. 横叉边(cross edge):上图中以蓝色边表示(即 9->7 ),搜索的时候遇到的一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先。
  4. 前向边(forward edge):上图中以绿色边表示(即 3->6),在搜索的时候遇到子树中的结点的时候形成的。搜索过程所有前向边组成一条搜索分支。

DFS生成树与强连通分量之间的关系:

如果结点 u 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 u为根的子树中。结点 u被称为这个强连通分量的根。

以下图的结构为例,讲解查找强连通分量的流程。

7.png

准备变量

  • sta,存储强连通分量上的所有节点;
  • dfn记录节点的时间戳,一个结点的子树内结点的 dfn 都大于该结点的 dfn。也可以记录节点是否被访问过。
  • low(回溯值)记录节点通过一条不在搜索树上的边能到达的结点。或者说不经过直接父节点能访问到的最早(远)的祖先,或者说是经过回边访问到的祖先节点。

搜索过程

  • 从节点1开始深度搜索,记录每一个节点在DFS时的时间戳以及回溯值。如1号节点的刚开始的时间戳为1,回溯值为1。别忘记了,1号节点现在也在栈中。

8.png

  • 按深度搜索路线,一路下去,后面应该是2、5、4。下图给出了当搜索到4号节点时,每一个节点的时间戳和回溯值以及栈中的状态。此时栈中由栈底到栈顶存储着一条DFS搜索树:1->2->5->4

9.png

  • 当从4号节点访问到2号节点时,转机出现了。因为,2节点被访问过,现又以4号节点的子节点身份重新被访问,会想到是不是碰到了祖先,或者说遇到了同一个强连通分量上的节点?

    答案是,不能这么简单的认为?因为这种情况有可能是回边也有可能是横叉边

    如下图所示。

    1号开始深度搜索,在第一条深度搜索分支结束后,4号节点也会被标记为被访问过。回溯到1号节点后,会开始第二条分支,在再次搜索到4号节点时,同样会发现4号节点也被访问。难道说4号节点和1号节点在同一个强连通分量上吗?4->2是回边,而1->4是横叉边。

13.png

​ 那么应该如何做出正确判断?继续回到我们的图结构上来讨论怎么正确得到强连通分量。

​ 下图中2号节点在栈中,说明早于4号节点被访问到且还没有加入其它的强连通分量上,可以判断24号的祖先。所以节点是否在栈中,是判断是不是回边的一个很重要的条件。

9.png

  • 于是,更新4号节点的low[4]=2。既然4号节点能到达2号节点,显然,点4的父节点们也能通过4号节点到达2号节点……一脉相承吗?于是这些节点的low值得以更新。

10.png

  • 4号节点除了2号节点外没有其它子节点,搜索结束,回溯到4号,因其dfs[4]!=low[4],暂时不要出栈,继续回溯到5号节点,因为dfs[5]!=low[5],不出栈。继续回溯到2号节点,因其dfs[2]==low[2],说明一个强连通分量到2号节点结束。把它们从栈中弹出来,得到第一个强连通分量上的所有节点。

**Tips:**如果 i 节点的dfn[i]!=low[i],说明其节点可以回到更早的祖先。也说明,其在以祖先开始的强连通分量上。所以只有一直回溯到祖先时,才能一一出栈。Tarjan算法求解强连通分量中,栈起到了至关重要的作用。

一旦发现一个强连通分量,就会把这个强连通分量上的节点弹出来。所以访问过、但是不在栈中的节点说明已经加入到了另一个强连通分量上。如果访问过,但是还在栈中的节点说明还没有找到归属。

11.png

  • 回溯到1号节点时,因dfn[1]=low[1]1号节点构成只有一个节点的强连通分量。

小结:

  • **深度搜索阶段:**如果 u节点的子节点v已经访问、且在栈中。说明vu的祖先,更新ulow值。同时更新u的除了v之外祖先的low值。
  • **回溯阶段:**如果u节点的dfn!=low,继续向上回溯, 如果u节点的dfn==low,说明找到了一个强连通分量,把栈中u节点(包含 u)之上的节点全部弹出来。

编码实现

#include<iostream>
#include<stack>
#include<vector>
using namespace std;
//节点数、边数
int n,m;
//dfn记数器和强连通分量记数器
int cnt,cntb;
//矩阵存储图
vector<int> edge[101];
//记录强连通分量
vector<int> connections[101];
//是否在栈中
bool inSta[101];
//时间戳
int dfn[101];
//回溯值
int low[101];
//栈
stack<int> s;

void getConn(int u) {
	++cnt;
	//前序位置记录节点的时间戳和回溯值
	dfn[u]=low[u]=cnt;
	//入栈
	s.push(u);
	inSta[u]=true;
	//遍历子节点
	for(int i=0; i<edge[u].size(); ++i) {
		int v=edge[u][i];
		if(!dfn[v]) {
			//没有被访问
			getConn(v);
			//回溯位置,根据子节点的 low 更新父节点的 lovw
			low[u]=min(low[u],low[v]);
		} else if(inSta[v])
			//访问过且在栈中,遇到了回边。更新 low 为祖先节点的时间戳
			low[u]=min(low[u],dfn[v]);
	}
	//后序遍历位置
	if(dfn[u]==low[u]) {
		//如果时间戳和回溯值相同,找到一条强连通分量
		++cntb;
		int t;
		do {
			t=s.top();
			s.pop();
			inSta[t]=false;
			connections[cntb].push_back(t);
		} while(t!=u);
	}
}
int main() {
	cin>>n>>m;
	for(int i=1; i<=m; ++i) {
		int u,v;
		cin>>u>>v;
		edge[u].push_back(v);
	}
	getConn(1);
	for(int i=1; i<=cntb; ++i) {
		cout<<"conn: "<<i<<" : ";
		for(int j=0; j<connections[i].size(); ++j)
			cout<<connections[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}
//测试数据
//7 11
//1 2
//2 3
//2 5
//2 4
//3 5
//3 7
//7 5
//5 6
//6 7
//4 1
//4 5

思考一下,如下图的强连通分量有几个。

14.png

答案:有两个,分别是

  • 6 5 4 3 2
  • 1

3. 总结

强连通分量算法还有Kosaraju 、Garbow 算法。有兴趣者可自行了解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/280014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pandas教程(一)—— 数据结构

前言 Pandas是贯穿数据分析的主要工具之一&#xff0c;它经常和其他数值计算工具一起使用&#xff08;例如&#xff1a;Numpy、SciPy和matplotlib&#xff09;。尽管pandas采用了很多NumPy的代码风格&#xff0c;但二者最大的区别是&#xff1a;pandas主要用于处理表格型或异质…

Typora快捷键设置详细教程

文章目录 一、快捷键设置步骤二、设置快捷键简单案例参考资料 一、快捷键设置步骤 在typora软件中&#xff0c;快捷键的设置步骤主要为&#xff1a; 打开【文件】–>【偏好设置】&#xff0c;找到【通用】–>【打开高级设置】&#xff0c;找到 conf.user.json 文件。 然…

系统启动流程 - 理解modules加载流程

​编辑 Hacker_Albert    202 linux 启动流程module加载 1.启动过程分为三个部分 BIOS 上电自检&#xff08;POST&#xff09;引导装载程序 (GRUB2)内核初始化启动 systemd&#xff0c;其是所有进程之父。 1.1.BIOS 上电自检&#xff08;POST&#xff09; BIOS stands for…

HTML5+CSS3②——图像、超链接、音频、视频

目录 图像 超链接 音频 视频 图像 作用&#xff1a;在网页中插入图片 单标签&#xff1a; 标签名&#xff1a;<img src"图片的URL"> <img src"图片的URL" alt"替换文本" title"提示文本"> 属性写在尖括号里面&#xff0c;…

使用SpringBoot AOP记录操作日志和异常日志

使用SpringBoot AOP记录操作日志和异常日志 平时我们在做项目时经常需要对一些重要功能操作记录日志&#xff0c;方便以后跟踪是谁在操作此功能&#xff1b;我们在操作某些功 能时也有可能会发生异常&#xff0c;但是每次发生异常要定位原因我们都要到服务器去查询日志才能找…

Stable Diffusion 系列教程 - 5 ControlNet

ControlNet和LORA的定位都是对大模型做微调的额外网络。作为入门SD的最后一块拼图是必须要去了解和开发的。为什么ControlNet的影响力如此的大&#xff1f;在它之前&#xff0c;基于扩散模型的AIGC是非常难以控制的&#xff0c;扩散整张图像的过程充满了随机性。这种随机性并不…

FFmpeg学习笔记--Centos8安装FFmpeg

1--安装指令 sudo yum install epel-releasesudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpmsudo yum install ffmpeg ffmpeg-develffmpeg -version 2--版本信息

【Transformer】深入理解Transformer模型2——深入认识理解(下)

前言 Transformer模型出自论文&#xff1a;《Attention is All You Need》 2017年 近年来&#xff0c;在自然语言处理领域和图像处理领域&#xff0c;Transformer模型都受到了极为广泛的关注&#xff0c;很多模型中都用到了Transformer或者是Transformer模型的变体&#xff0…

python可视化界面自动生成,python如何做可视化界面

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python gui可视化操作界面制作&#xff0c;python做出的炫酷的可视化&#xff0c;现在让我们一起来看看吧&#xff01; 目录 前言 一.环境配置 插件&#xff1a; 1.python 2.Chinese 3.Open In Default Browser 安装pyt…

zabbix添加监控主机(agent)并告警

一、添加监控主机 总体来说&#xff0c;在被监控主机上安装部署zabbix-agent&#xff0c;并修改配置文件&#xff08;zabbix_agentd.conf&#xff09;的参数。然后在zabbix 服务端zabbix-get检查是否可以监控。如果可以了&#xff0c;就可以在web页面添加了&#xff0c;要监控…

据报道,微软的下一代 Surface 笔记本电脑将是其首款真正的“人工智能 PC”

明年&#xff0c;微软计划推出 Surface Laptop 6和 Surface Pro 10&#xff0c;这两款设备将提供 Arm 和 Intel 两种处理器选项。不愿意透露姓名的不透露姓名人士透露&#xff0c;这些新设备将引入先进的人工智能功能&#xff0c;包括配备下一代神经处理单元 (NPU)。据悉&#…

vue-mixin(混入---入门到离职系列)

定义 官方定义(官网定义:组合选项 | Vue.js (vuejs.org)) mixins 选项接受一个 mixin 对象数组。这些 mixin 对象可以像普通的实例对象一样包含实例选项&#xff0c;它们将使用一定的选项合并逻辑与最终的选项进行合并。举例来说&#xff0c;如果你的 mixin 包含了一个 create…

【Vue】computed详解

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

jQuery日历签到插件下载

jQuery日历签到插件下载-遇见你与你分享

听GPT 讲Rust源代码--src/tools(39)

File: rust/src/tools/rustfmt/src/config/config_type.rs 在Rust代码中&#xff0c;rust/src/tools/rustfmt/src/config/config_type.rs文件的作用是定义了与配置相关的数据结构和函数。 Config struct&#xff08;配置结构体&#xff09;&#xff1a;该结构体用于存储rustfmt…

WEB渗透—PHP反序列化(十一)

Web渗透—PHP反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩…

FTP的基本介绍

FTP ftp的介绍&#xff1a; ftp是一个可以提供共享文件的服务器&#xff0c;他可以通过iis.msc也就是windows 的服务器管理器来打开&#xff0c;或者通过cmd命令行打开 如何使用iis.msc打开ftp&#xff0c;如何使用cmd打开ftp &#xff0c;如何匿名登录ftp&#xff0c;ftp和…

MySQL入门教程-触发器

9.触发器 什么是触发器 触发器(trigger)&#xff1a;监视某种情况&#xff0c;并进行某种操作&#xff0c;它的执行并不是程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#xff0c;例如&#xff1a;对一张表进行操作&#xff08;插入&#xff0c;更新&…

关于表格太大了jupyter无法单次处理的问题

记录下自己的心路历程…耗时耗精力 我用的数据库单个表格就很大&#xff0c;一个表格有30多G&#xff0c;jupyter无法处理这么大的表格&#xff0c;会直接把电脑的进程全部结束掉&#xff0c;结束掉要是能运行成功倒也行啊&#xff0c;然鹅…给我报错说处理不了&#xff0c;罢工…

Rust之构建命令行程序(二):读取文件

开发环境 Windows 10Rust 1.74.1 VS Code 1.85.1 项目工程 这次创建了新的工程minigrep. 读取文件 现在&#xff0c;我们将添加读取file_path参数中指定的文件的功能。首先&#xff0c;我们需要一个样本文件来测试它:我们将使用一个包含少量文本的文件&#xff0c;多行包含一…