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 更新父节点的 lovwlow[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 算法。有兴趣者可自行了解。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LAMP集中式搭建+LNMP分布式搭建(新版)

LAMP搭建LNMP搭建 LAMP搭建LNMP搭建一、LAMP搭建(集中式)1、LAMP简介2、LAMP组件及作用3、编译安装Apache httpd服务4、编译安装mysqld 服务5、编译安装PHP解析环境6、安装论坛7、安装博客 二、LNMP搭建(分布式)1、LNMP工作原理2、安装nginx3、安装mysql4、安装php5、在浏览器测…

《深入理解Java虚拟机(第三版)》读书笔记:虚拟机类加载机制、虚拟机字节码执行引擎、编译与优化

下文是阅读《深入理解Java虚拟机&#xff08;第3版&#xff09;》这本书的读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 第6章 类文件结构第7章 虚拟机类加载机制7.2 类加载的时机7.3 类加载的过程7.4 类加载器7.5 Java模块化系统 第8章 虚拟机字节码执…

C++求解数组长度的方法(包含求解vector和字符串求解的方法)

对于一个C的初学者&#xff0c;在如何求解数组长度问题上可能会稍有混乱&#xff0c;因为求解长度的方法有很多&#xff0c;比如size()、length()、sizeof()等函数&#xff0c;今天我就和大家一起学习如何求解数组长度&#xff0c;并探讨size()、length()、sizeof()这三个函数的…

【Linux驱动】设备树简介 | 内核对设备树的处理

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f9f2;设备树简介&#x1f3f9;设备树语法&#x1f3f9;常见节点和属性&#x1f3f9…

LOAM: Lidar Odometry and Mapping in Real-time 论文阅读

论文链接 LOAM: Lidar Odometry and Mapping in Real-time 0. Abstract 提出了一种使用二维激光雷达在6自由度运动中的距离测量进行即时测距和建图的方法 距离测量是在不同的时间接收到的&#xff0c;并且运动估计中的误差可能导致生成的点云的错误配准 本文的方法在不需要高…

Educational cf 160的B题

Problem - B - Codeforces 找到最小操作次数&#xff0c;使得子串对应位与原来字符串对应位不相同。 交换是没有代价的&#xff0c;但是删除有代价。 首先复制两个一模一样的串&#xff0c;我们把下面作为固定串&#xff0c;然后对串中0和1的个数进行计数&#xff0c;由于我…

私有部署ELK,搭建自己的日志中心(六)-- 引入kafka对采集日志进行削峰填谷

一、背景 首先&#xff0c;要说明一点&#xff0c;elk日志中心&#xff0c;是可以缺少kafka组件的。 其次&#xff0c;如果是研发环境下&#xff0c;机器资源紧张的情况下&#xff0c;也是可不部署kafka。 最后&#xff0c;因为kafka的部署是可以独立的&#xff0c;所以本文将…

介绍一款PDF在线工具

PDF是我们日常工作中的一种常见格式&#xff0c;其处理也是我们工作的重要基础性环节&#xff0c;一款可靠的处理工具显得十分重要。 完全免费、易于使用、丰富的PDF处理工具&#xff0c;包括&#xff1a;合并、拆分、压缩、转换、旋转和解锁PDF文件&#xff0c;以及给PDF文件…

IntelliJ IDEA常用快捷键

【1】创建内容&#xff08;新建&#xff09;&#xff1a;altinsert 【2】main方法&#xff1a;psvm 【3】输出语句&#xff1a;sout 【4】复制行&#xff1a;ctrld 【5】删除行&#xff1a;ctrly&#xff08;很多编辑器ctrly是前进操作&#xff0c;如果选择 Delete Line&…

05-C++ 类和对象-继承

类与对象-03 继承与派生 1. 继承的概念 c最重要的特征是代码重用&#xff0c;通过继承机制可以利用已有的数据类型&#xff0c;来定义新的数据类型&#xff0c;新的类不仅拥有旧类的成员&#xff0c;还拥有新定义的成员。 一个 B 类继承于 A 类&#xff0c;或称从类 A 派生…

数字人私人定制

数字人是什么&#xff1f; 在回答这个问题之前&#xff0c;我们先回答另一个问题&#xff0c;人如何与人工智能交流&#xff1f;目前可以通过文字、语音、电脑屏幕、手机屏幕、平板、虚拟现实设备等和人工智能交流&#xff0c;为了得到更好的交流体验&#xff0c;人工智能必然…

php 8.4 xdebug扩展编译安装方法

最新版php8.4 xdebug扩展只能通过编译方式安装, pecl是安装不了的, 编译方法如下 下载最新版xdebug git clone https://github.com/xdebug/xdebug.git 却换入xdebug目录执行编译安装xdebug cd xdebug phpize./configure --enable-xdebugmakemake install3. 配置启用xdebug 这…

使用element中el-cascader级联选择器实现省市区街道筛选(非动态加载)

<template><el-form ref"form" :model"form" label-width"80px"><el-form-item label"地址:" prop"addressList"><el-cascaderv-model"form.addressList":props"props":options&q…

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

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

GBASE南大通用-GBase 8s数据库日志模式及切换

一、 GBase 8s数据库共有以下 4 种日志模式&#xff1a;无日志模式、缓冲日志模式、无缓冲日志模式、ANSI 模式。详细介绍如下&#xff1a; 1、无日志模式&#xff08;Non logging&#xff09;&#xff1a; 采用无日志模式时&#xff0c;所有 DML 操作都不会被记录到日志中&…

IP地理位置定位技术基本原理

IP地理位置定位技术的基本原理是基于IP地址的特性。每个IP地址在网络中都有一个与之对应的地理位置信息&#xff0c;这是通过IP地址数据库来确定的。这个数据库由ISP&#xff08;Internet Service Provider&#xff09;或其它一些机构维护&#xff0c;其中包含了每个IP地址的地…

链表精选题集

目录 1 链表翻转 题目链接&#xff1a; 解题&#xff1a; 试错版&#xff1a; 2 找中间节点 题目链接: 题解&#xff1a; 3 找倒数第k个节点 题目链接&#xff1a; 题解&#xff1a; 4 将两个升序链表合并为一个升序链表 题目链接&#xff1a; 题解&#xff1a; …

tmux 包的介绍及使用

tmux 本博文参照 https://blog.csdn.net/qq_43912191/article/details/123214679 对 tmux 进行总结和归纳。 tmux&#xff08;Terminal Multiplexer&#xff09;是一款命令行下的终端复用软件&#xff0c;用于在一个终端窗口中运行多个终端会话&#xff0c;并且可以在各会话之…

第六课:冷战和消费主义、个人计算机革命、图形用户界面(GUI)及3D图形

第六课&#xff1a;冷战和消费主义、个人计算机革命、图形用户界面&#xff08;GUI&#xff09;及3D图形 第二十四章&#xff1a;冷战和消费主义本课概括&#xff1a;政府和消费者推动了计算机的发展 第二十五章&#xff1a;个人计算机革命本集概括&#xff1a;继续讲计算机发展…

AJAX: 整理2:学习原生的AJAX,这边借助express框架

1. npm install express 终端直接安装 2. 测试案例&#xff1a;Hello World&#xff01; 新建一个express.js的文件&#xff0c;写入下方的内容 // 1. 引入express const express require(express)// 2. 创建服务器 const app express()// 3.创建路由规则 // request 是对请…