算法随笔:强连通分量

概念和性质:

 

强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。

强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。

强连通分量的一些性质

(1)一个点必须有出度和入度,才会与其他点强连通。

(2)把一个SCC从图中挖掉,不影响其他点的强连通性。

求强连通分量的方法:

Kosaraju算法:

算法原理及步骤:

(1)将有向图G的所有边反向,建立返图rG,反图rG不会改变原图G的强连通性(也就不会改变SCC的数量)。

(2)对原图G做一次DFS,确定各点的先后顺序(可以用vector数组来记录顺序)。

(3)确定了顺序之后,在反图上做DFS,按顺序从优先级最高的点开始,到最低的点。

为什么要在反图上做DFS?这样做可以求得被隔离的岛。我们以下图为例,图a为原图G,图b为反图rG,我们将每个SCC都打上阴影,将其想象成一个个岛屿(也可以理解为一个个缩点),那么我们可以发现{a,b,e}这个SCC与其他SCC之间只有出边没有入边,所以这个SCC在第二次DFS中是要首先被遍历的,

那么我们在反图上遍历它的好处就是,可以发现在反图中,{a,b,e}这个SCC变成了只有入边没有出边,所以我们的遍历一定只会被限制在当前的SCC中,确定了第一个SCC。当我们遍历完这个SCC,我们将其删除(在代码中可以体现为将其标记为已经遍历),然后继续从剩下的优先级最高的点开始搜索,这一次从c开始搜索,因为反边,这次搜索也被限制在{c,d}内,确定了第二个SCC,删除这个SCC,然后按照这个步骤确定剩下的SCC。

参考代码:

题目来自hdu 1269,标准的模板题,判断整个图是否强连通,求出SCC数量是否为1即可。

在进行第一次dfs确定先后顺序时,我们有在递归进入时和递归返回时标记两种方法。分别给出代码:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
vector<int> G[maxn], rG[maxn];  // 原图G和它的反图
vector<int> S;      // 存储第一次dfs的结果:标记点的先后顺序
int vis[maxn];      // 访问标记
int sccno[maxn];    // sccno[i]表示第i个点所属的强连通分量 
int cnt;            // 强连通分量的个数
int n, m;void dfs1(int u) {  // 在原图G上做一次dfs,标记点的先后顺序S.push_back(u);     // 记录点的先后顺序vis[u] = true;for (int i = 0; i < G[u].size(); i++) {if (!vis[G[u][i]]) {dfs1(G[u][i]);}}
}void dfs2(int u) {  // 在反图rG上做一次dfs,顺序从标记最大的点开始,到标记最小的点。sccno[u] = cnt;for (int i = 0; i < rG[u].size(); i++) {if (!sccno[rG[u][i]]) {dfs2(rG[u][i]);} }
}void Kosaraju() {cnt = 0;S.clear();memset(sccno, 0, sizeof sccno);memset(vis, 0, sizeof vis);for (int i = 1; i <= n; i++) {if (!vis[i]) {dfs1(i);}}for (int i = 0; i < n; i++) {if (!sccno[S[i]]) {cnt++;dfs2(S[i]);}}
}void solve() {int u, v;while (cin >> n >> m, n != 0 || m != 0) {for (int i = 1; i <= n; i++) {G[i].clear();rG[i].clear();}for (int i = 0; i < m; i++) {cin >> u >> v;G[u].push_back(v);      // 原图rG[v].push_back(u);     // 反图}Kosaraju();cout << (cnt == 1 ? "Yes\n" : "No\n");}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
vector<int> G[maxn], rG[maxn];  // 原图G和它的反图
vector<int> S;      // 存储第一次dfs的结果:标记点的先后顺序
int vis[maxn];      // 访问标记
int sccno[maxn];    // sccno[i]表示第i个点所属的强连通分量 
int cnt;            // 强连通分量的个数
int n, m;void dfs1(int u) {  // 在原图G上做一次dfs,标记点的先后顺序vis[u] = true;for (int i = 0; i < G[u].size(); i++) {if (!vis[G[u][i]]) {dfs1(G[u][i]);}}S.push_back(u);     // 记录点的先后顺序,标记大的放在S的后面
}void dfs2(int u) {  // 在反图rG上做一次dfs,顺序从标记最大的点开始,到标记最小的点。sccno[u] = cnt;for (int i = 0; i < rG[u].size(); i++) {if (!sccno[rG[u][i]]) {dfs2(rG[u][i]);} }
}void Kosaraju() {cnt = 0;S.clear();memset(sccno, 0, sizeof sccno);memset(vis, 0, sizeof vis);for (int i = 1; i <= n; i++) {if (!vis[i]) {dfs1(i);}}for (int i = n - 1; i >= 0; i--) {if (!sccno[S[i]]) {cnt++;dfs2(S[i]);}}
}void solve() {int u, v;while (cin >> n >> m, n != 0 || m != 0) {for (int i = 1; i <= n; i++) {G[i].clear();rG[i].clear();}for (int i = 0; i < m; i++) {cin >> u >> v;G[u].push_back(v);      // 原图rG[v].push_back(u);     // 反图}Kosaraju();cout << (cnt == 1 ? "Yes\n" : "No\n");}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}

Tarjan算法:

算法原理及步骤:

在讲Tarjan算法之前,我们需要给出一个定理:一个SCC,从其中任意一点出发,都至少有一条路径可以回到自己。那么其实从任意一点开始DFS,这个点都会成为这个SCC的祖先。

我们可以在DFS求low值的同时把点按SCC(有相同的low值)分开,用栈分离不同的SCC。关于low值的定义可以看看我之前关于割点割边的随笔。

大体步骤:

dfn[u]表示dfs时达到顶点u的次序号(时间戳),low[u]表示以u为根节点的dfs树中次序号最小的顶点的次序号,所以当dfn[u]=low[u]时,以u为根的搜索子树上所有节点是一个强连通分量。 先将顶点u入栈,dfn[u]=low[u]=++idx,扫描u能到达的顶点v,如果v没有被访问过,则dfs(v),low[u]=min(low[u],low[v]),如果v在栈里,low[u]=min(low[u],dfn[v]),扫描完v以后,如果dfn[u]=low[u],则将u及其以上顶点出栈。

考虑最先入栈的点,每进入一个新的SCC,访问并入栈的第1个点都是这个SCC的祖先,它的num值和low值相等,这个SCC中所有的点的low值都与它相等。

参考代码:

例题仍然是hdu1269

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
int num[maxn];          // 记录每个节点的dfs次序,时间戳
int low[maxn];          // low[v]表示v以及v的后代能退回到的节点的num值最小是多少
int sccno[maxn];        // sccno[u]表示u点属于哪个强连通分量
int st[maxn], top;   // 模拟栈
vector<int> G[maxn];    // 图
int dfn;                // dfs次序,时间戳
int cnt;                // 强连通分量的个数
int n, m;void dfs(int u) {st[top++] = u;  // u入栈low[u] = num[u] = ++dfn;for (int i = 0; i < G[u].size(); i++) {int v = G[u][i];if (!num[v]) {  // 未访问过的点,继续dfsdfs(v);     // dfs的最底层,是最后一个SCClow[u] = min(low[u], low[v]);} else if (!sccno[v]) {     // 处理回退边low[u] = min(low[u], num[v]);}}if (low[u] == num[u]) {     // 栈底的点是SCC的祖先,它的low == numcnt++;while (1) {int v = st[--top];  // v弹出栈sccno[v] = cnt;if (u == v) {       // 栈底的点是SCC的祖先break;}}}
}void tarjan() {cnt = top = dfn = 0;memset(sccno, 0, sizeof sccno);memset(num, 0, sizeof num);memset(low, 0, sizeof low);for (int i = 1; i <= n; i++) {if (!num[i]) {dfs(i);}}
}void solve() {int u, v;while (cin >> n >> m, n != 0 || m != 0) {for (int i = 1; i <= n; i++) {G[i].clear();}for (int i = 0; i < m; i++) {cin >> u >> v;G[u].push_back(v);}tarjan();cout << (cnt == 1 ? "Yes\n" : "No\n");}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}

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

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

相关文章

第 7 章 排序算法(1)(介绍,分类,时间复杂度,空间复杂度)

7.1排序算法的介绍 排序也称排序算法(Sort Algorithm)&#xff0c;排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 7.2排序的分类&#xff1a; 内部排序: 指将需要处理的所有数据都加载到**内部存储器(内存)**中进行排序。外部排序法&#xff1a; 数据量过大&am…

基于CentOS搭建私有仓库harbor

环境&#xff1a; 操作系统&#xff1a;CentOS Linux 7 (Core) 内核&#xff1a; Linux 3.10.0-1160.el7.x86_64 目录 安装搭建harbor &#xff08;1&#xff09;安装docker编排工具docker compose &#xff08;2&#xff09;下载Harbor 安装包 &#xff08;3&…

chapter 3 Free electrons in solid - 3.1 自由电子模型

3.1 自由电子模型 Free electron model 研究晶体中的电子&#xff1a; 自由电子理论&#xff1a;不考虑离子实能带理论&#xff1a;考虑离子实&#xff08;周期性势场&#xff09;的作用 3.1.1 德鲁德模型 Drude Model - Classical Free Electron Model (1)德鲁德模型 德鲁…

【3Ds Max】可编辑多边形“边”层级的简单使用

目录 简介 示例 1. 编辑边 &#xff08;1&#xff09;插入顶点 &#xff08;2&#xff09;移除 &#xff08;3&#xff09;分割 &#xff08;4&#xff09;挤出 &#xff08;5&#xff09;切角 &#xff08;6&#xff09;焊接 &#xff08;7&#xff09;桥 &…

NPM 管理组织成员

目录 1、向组织添加成员 1.1 邀请成员加入您的组织 1.2 撤销组织邀请 2、接收或拒接组织邀请 2.1 接收组织邀请 2.2 拒绝组织邀请 3、组织角色和权限 4、管理组织权限 5、从组织中删除成员 1、向组织添加成员 作为组织所有者&#xff0c;您可以将其他npm用户添加到…

阿里云访问端口被限制解决方法记录

阿里云服务器&#xff0c;80端口可以访问&#xff0c;但是加入了安全组端口8080 通过公网访问改端口策略&#xff0c;发现不能被访问 问题出在防火墙&#xff0c;需要重置一下 解决方法&#xff1a; 在运行的服务器上执行如下命令&#xff1a; # iptables -A INPUT -j ACCEP…

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架&#xff0c;复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增…

Android Studio调试出现错误时,无法定位错误信息解决办法

做项目时运行项目会出现问题&#xff0c;但是找不到具体位置&#xff0c;如下图所示&#xff1a;感觉是不是很懵逼~&#xff0c;Log也没有显示是哪里的问题 解决方案&#xff0c;在右侧导航栏中选择Gradle——app——build&#xff0c;然后点击运行 运行结果如下&#xff0c;很…

Stable Diffusion 系列教程 | 图生图基础

前段时间有一个风靡全网的真人转漫画风格&#xff0c;受到了大家的喜欢 而在SD里&#xff0c;就可以通过图生图来实现类似的效果 当然图生图还有更好玩的应用&#xff0c;我们一点一点来探索 首先我们来简单进行一下图生图的这一个实践---真人转动漫 1. 图生图基本界面 和…

iOS代码混淆

文章目录 一、混淆的原理二、实现混淆1. 创建文件2. 将文件拖导入目录中3. 将以下脚本拷贝到刚新建的confuse.sh文件中4. 修改文件权限5. 修改项目配置6. 添加需要混淆的方法名7. 配置PCH文件8. 运行效果 一、混淆的原理 这里使用的混淆的原理是&#xff0c;用一串随机生成的字…

WPS中的表格错乱少行

用Office word编辑的文档里面包含表格是正常的&#xff0c;但用WPS打开里面的表格就是错乱的&#xff0c;比如表格位置不对&#xff0c;或者是表格的前几行无法显示、丢失了。 有一种可能的原因是&#xff1a; 表格属性里面的文字环绕选成了“环绕”而非“无”&#xff0c;改…

行业追踪,2023-08-22

自动复盘 2023-08-22 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

深度学习3:激活函数

一、激活函数的简介与由来 激活函数&#xff1a;是用来加入非线性因素的&#xff0c;解决线性模型所不能解决的问题。 线性函数的组合解决的问题太有限了&#xff0c;碰到非线性问题就束手无策了。如下图。 通过激活函数映射之后&#xff0c;可以输出非线性函数。 最后再通过…

IDEA项目实践——Element UI概述

系列文章目录 IDEA项目实践——JavaWeb简介以及Servlet编程实战 IDEA项目实践——Spring当中的切面AOP IDEA项目实践——Spring框架简介&#xff0c;以及IOC注解 IDEA项目实践——动态SQL、关系映射、注解开发 IDEWA项目实践——mybatis的一些基本原理以及案例 文章目录 …

Linux系统USB摄像头测试程序(三)_视频预览

这是在linux上usb摄像头视频预览程序&#xff0c;此程序用到了ffmpeg、sdl2、gtk3组件&#xff0c;程序编译之前应先安装他们。 #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <zconf.h> …

通过postgresql的Ltree字段类型实现目录结构的基本操作

通过postgresql的Ltree字段类型实现目录结构的基本操作 将这种具有目录结构的excel表存储到数据库中&#xff0c;可以采用树型结构存储 DROP TABLE IF EXISTS "public"."directory_tree"; CREATE TABLE "public"."directory_tree" (…

产品流程图是什么?怎么做?

产品流程图是什么&#xff1f; 产品流程图是一种图形化的表达方式&#xff0c;用于描述产品开发、制造、销售、使用等各个阶段中涉及的流程、步骤和关系。它通过图形符号、箭头、文本等元素&#xff0c;展示了产品的各个环节之间的关联和顺序&#xff0c;通常被用于可视化产…

lwIP更新记10:IP 冲突检测

lwip-2.2.0-rc1 版本于 2023 年 6 月 29 日发布&#xff0c;带来了我期盼已久的 IPv4 冲突检测 功能。 lwip-2.2.0-rc1 版本重新回归了 master 分支&#xff08;主分支&#xff09;&#xff0c;不再使用单独的稳定分支。 master 分支 是一个 Git&#xff08;版本控制程序&…

[保研/考研机试] KY196 复数集合 北京邮电大学复试上机题 C++实现

题目链接&#xff1a; 复数集合_牛客题霸_牛客网 一个复数&#xff08;xiy&#xff09;集合&#xff0c;两种操作作用在该集合上&#xff1a; 1、Pop 表示读出集。题目来自【牛客题霸】https://www.nowcoder.com/share/jump/437195121692724009060 描述 一个复数&#xff08;…

如何做好流量经营?数字化系统如何加速流量增长

​在用户转化策略上&#xff0c;从“公域流量”到“私域流量”的来源转变&#xff0c;充分说明企业已经意识到公域流量存在成本高、粘度差、稳定性差等问题&#xff0c;开始寻求拥有更低成本、更容易培养忠实度、更容易精准触达的私域流量。但由于企业缺少整体、系统化的私域经…