【算法每日一练]-图论(保姆级教程篇11 tarjan模板篇)无向图的桥 #无向图的割点 #有向图的强连通分量

目录

预备知识

模板1:无向图的桥

模板2:无向图的割点

模板3:有向图的强连通分量


        

        

讲之前先补充一下必要概念:

预备知识

无向图的【连通分量】:
即极大联通子图,再加入一个节点就不再连通(对于非连通图一定两个以上的连通分量)

无向图的【(割边或)桥】:
即去掉该边,图就变成了两个连通子图

无向图的【割点】:
将该点和相关联的边去掉,图将变成两个及以上的子图
注意:有割点不一定有桥,但是有桥一定有割点

        
无向图的【边双连通图】:
无向图中不存在桥,即删除一条边后仍然连通(每两个点间有至少两条路径,且路径上的边互不重复)    

        
无向图的【点双连通图】:
无向图中不存在割点,删除一个点后仍然联通(顶点大于2时,每两个点间有至少两条路径,且路径上的点互不重复) 

        
 【边双连通分量eDDC】:极大边双连通图 
 【点双连通分量vDDC】: 极大点双连通图

        
无向图的【缩点】1:
把每个【eDDC】看成一个点,桥看成连接两个点的无向边,得到一棵树。这种方式称为eDCC缩点

无向图的【缩点】2:
把每个【vDDC】看成一个点,割点看成一个点,每个割点向包含它vDDC连接一条边,得到一棵树。这种方式称为vDCC缩点

有向图的【强连通分量】:

即极大连通子图(任何两个节点都能相互到达)

        

模板1:无向图的桥

 首先介绍时间戳和回溯点(我更喜欢叫它走回点,听我往下分析)

dfn[u] (时间戳) 表示u节点深度优先遍历最先访问的序号,

low[u](走回点)表示u节点或子孙能走回到的最小节点序号。

给出结论:

无向边<x,y>是桥,当搜索树上存在x的某个子节点y满足 low[y]>dfn[x](没有等号)

怎么理解呢?
首先就是你不能走父子边回去,也就是怎么来的怎么回去,这样肯定是不行的。我们必须要走非父子边回去的。因为当从非父子边能回去时,也就意味着走到了环,那么此时就获取能回到的最小dfn为low,然后回溯时候父节点获取孩子更小的low即可。相当于走到环了,那么把这个最小的dfn传遍整个环中,也就是整个连通分量中。从而表示u节点或子孙能走回到的最小节点序号。这样的话同一个连通分量中low值是一样的,是最早走回的点号!(看不懂可以画一下图,就理解了)
        

对于无向图的桥:孩子的low值比父亲的dfn值大就说明有桥(这个可怜的孩子不能走非父子边回家了,能不可怜吗??)

void tarjan(int u,int fa){dfn[u]=low[u]=++num;//初始化for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(v==fa) continue;//不可以走父子边回去if(!dfn[v]){//没访问过就递归访问,访问完要更新lowtarjan(v,u);low[u]=min(low[u],low[v]);//获取孩子最小的low值(low是自己或子孙能走回的最小dfn嘛,不是回溯)if(low[v]>dfn[u]){//孩子的low值比父亲的dfn值大说明有桥cout<<u<<"- "<<v<<"is bridge "<<'\n';}	}else{//可以从非父子边回去,就要获取dfn值(就是该点能回到的最小dfn,不是回溯)low[u]=min(low[u],dfn[v]);}}
}

这段代码把“回溯大法”体现的很好。可以看到没访问过就递归访问,返回后,也就是等到孩子们的low值都变正确了,我再去更新自己的low。

另外那一句else其实是留给遇环来处理的,父亲节点根本用不上,你只需要在孩子们遇环后把它们自己“照顾好了”之后,借助它们的结果来优化自己就行了。

        

完整代码 

#include <bits/stdc++.h>//无向图的桥
using namespace std;
const int maxn=1000+5;
int n,m;
int head[maxn],cnt;
struct node{int to,next;}e[maxn*2];
int low[maxn],dfn[maxn],num;
//dfn[u](时间戳)表示u节点深度优先遍历访问的序号
//low[u](走回点)表示u节点或子孙能走回到的最小节点序号void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}
void tarjan(int u,int fa){dfn[u]=low[u]=++num;//初始化for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(v==fa) continue;//不可以走父子边回去if(!dfn[v]){//没访问过就递归访问,访问完要更新lowtarjan(v,u);low[u]=min(low[u],low[v]);//获取孩子最小的low值(low是自己或子孙能走回的最小dfn嘛,不是回溯)if(low[v]>dfn[u]){//孩子的low值比父亲的dfn值大说明有桥cout<<u<<"- "<<v<<"is bridge "<<'\n';}	}else{//可以从非父子边回去,就要获取dfn值(就是该点能回到的最小dfn,不是回溯)low[u]=min(low[u],dfn[v]);}}
}void init(){memset(head,0,sizeof(head));memset(low,0,sizeof(low));memset(dfn,0,sizeof(dfn));cnt=num=0;
}int main(){while(cin>>n>>m){init();int u,v;while(m--){cin>>u>>v;add(u,v);add(v,u);}for(int i=1;i<=n;i++){//因为不一定整个图都联通,所以要判断那些点是否走不到if(!dfn[i]) tarjan(i,i);//深度优先搜索树的起点就是树根}}	
}

可以输入数据或自己画图试一下
7 7
1 4
1 2
2 3
3 5
5 7
5 6
6 4

7 6
1 2
2 3
3 5
5 7
5 6
6 4 

        

        

模板2:无向图的割点

x是割点条件:x不是根节点 且搜索树上存在x的一个子节点y,满足low[y]>=dfn[x]

【或者】x是根节点 且搜索树上存在至少两个子节点满足上述条件 (画图可证明,这里不多说了)

#include <bits/stdc++.h>//无向图的割点
using namespace std;
const int maxn=1000+5;
int n,m,root;
int head[maxn],cnt;
struct node{int to,next;}e[maxn*2];
int low[maxn],dfn[maxn],num;void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}
void tarjan(int u,int fa){dfn[u]=low[u]=++num;//初始化int count=0;//记录有几个满足条件的子节点for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(v==fa) continue;//没访问过就递归访问,访问完更新lowif(!dfn[v]){tarjan(v,u);low[u]=min(low[u],low[v]);//low是自己或子孙能走回的最小dfnif(low[v]>=dfn[u]){count++;if(u!=root||count>1)cout<<u<<"is gepoint "<<'\n';}	}else{//可以从非父子边回去,就要获取dfn值low[u]=min(low[u],dfn[v]);}}
}void init(){memset(head,0,sizeof(head));memset(low,0,sizeof(low));memset(dfn,0,sizeof(dfn));cnt=num=0;
}int main(){while(cin>>n>>m){init();int u,v;while(m--){cin>>u>>v;add(u,v);add(v,u);}for(int i=1;i<=n;i++){//因为不一定整个图都联通,所以要判断那些点是否走不到if(!dfn[i]) {root=i;tarjan(i,i);}}}	
}

测试数据(供你参考使用)
7 7
1 4
1 2
2 3
3 5
5 7
5 6
6 4

5 3
1 2
2 3
3 5

        

        

        

模板3:有向图的强连通分量

有向图的【强连通分量】:即极大连通子图(任何两个节点都能相互到达)
        

if(low[u]==dfn[u]){//在回溯之前,在low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是一个连通分量int v;do{//输出强连通分量(一定要先执行再判断)v=s.top();s.pop();cout<<"V: "<<v<<' ';ins[v]=0;}while(v!=u);//直到和自己不等为止cout<<'\n';}

前面和无向图基本一样(无非不用跳过走父子边步骤),后面内容就不太一样了。

判断条件:在low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是一个连通分量。

#include <bits/stdc++.h>//有向图的强连通分量
using namespace std;
const int maxn=1000+5;
bool ins[maxn];
int n,m;
int head[maxn],cnt;
stack <int> s;
struct node{int to,next;}e[maxn*2];
int low[maxn],dfn[maxn],num;void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}void tarjan(int u){dfn[u]=low[u]=++num;//dfn访问序号,low使能回溯到的最早的dfnins[u]=1;s.push(u);//第一次访问节点时候入栈for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(!dfn[v]){//没访问过就递归访问tarjan(v);low[u]=min(low[u],low[v]);//获取孩子的最小的low值   }else if(ins[v]){//已经访问过且在栈中获取dfn号low[u]=min(low[u],dfn[v]);}}if(low[u]==dfn[u]){//在回溯之前,在low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是一个连通分量int v;do{//输出强连通分量(一定要先执行再判断)v=s.top();s.pop();cout<<"V: "<<v<<' ';ins[v]=0;}while(v!=u);//直到和自己不等为止cout<<'\n';}
}void init(){memset(head,0,sizeof(head));memset(low,0,sizeof(low));memset(ins,0,sizeof(ins));memset(dfn,0,sizeof(dfn));cnt=num=0;
}int main(){while(cin>>n>>m){init();int u,v;while(m--){cin>>u>>v;add(u,v);}for(int i=1;i<=n;i++){//有向图不一定整个图都双向联通,所以要判断那些点是否走不到if(!dfn[i]) 	tarjan(i);}}	
}

参考数据
5 8
1 2
1 3
3 2
3 4
3 5
4 1
4 5
5 1

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

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

相关文章

Python 删除字典列表等元素问题

""" 在 Python 中&#xff0c;在迭代列表的同时修改它可能导致意外的结果&#xff0c; 因为这会破坏迭代器的状态。在你的代码中&#xff0c;当你尝试删除元素时&#xff0c; 迭代器可能会跳过下一个元素&#xff0c;导致删除的不是你预期的元素。 ""…

Kafka Schema介绍

Avro概述 简介 Apache Avro(以下简称 Avro,读音:阿夫罗)是一个数据序列化系统,是一种与编程语言无关的序列化格式,是提供一种共享数据文件的方式。Avro是Hadoop中的一个子项目,Avro是一个基于二进制数据传输高性能的中间件。Avro可以做到将数据进行序列化,适用于远程…

三星AI笔电:年底大战一触即发,行业变革在即

随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;已逐渐渗透至我们日常生活的各个角落&#xff0c;而其在电脑行业的应用也正引发一场革命。 据韩国媒体Businesskorea报道&#xff0c;全球科技巨头三星电子即将于12月中发表世界首款搭载AI技术的笔电。此次发…

Leetcode1423. 可获得的最大点数

Every day a Leetcode 题目来源&#xff1a;1423. 可获得的最大点数 解法1&#xff1a;前缀和 后缀和 基于贪心的思想&#xff0c;要使得获得的点数最大&#xff0c;每次拿卡牌都应该选点数尽量高的卡牌。 但是拿卡牌有限制&#xff0c;每次行动&#xff0c;只可以从行的…

[Java][项目][战斗逻辑]基于JFrame的文字游戏

项目注解&#xff1a; Core:启动文件 AttributeBean&#xff1a;玩家属性 BackpackedBean&#xff1a;背包设计&#xff08;未完成&#xff09; BackpackedFrame&#xff1a;背包页面&#xff08;未完成&#xff09; BattleField&#xff1a;战斗逻辑&#xff08;核心&…

Android Chips(标签)

目录 一、流式布局标签发展历程 二、类型及使用 2.1 Chip.Action(默认值) 2.2 Chip.Entry 2.3 Chip.Filter 2.4 Chip.Choice 三、常用事件 3.1 OnClickListener 3.2 OnCheckedChangeListener 3.3 OnCloseIconClickListener 四、ChipGroup 4.1 ChipGroup Chip.Choi…

AGI = 大模型 + 知识图谱 + 强化学习

一、大模型&#xff08;Large Models&#xff09; 定义&#xff1a; 大模型通常指的是参数数量庞大的机器学习模型&#xff0c;特别是深度学习模型。这些模型在训练时需要大量的计算资源和数据。例如&#xff0c;GPT-3&#xff08;Generative Pre-trained Transformer 3&#…

力扣973. 最接近原点的 K 个点(java 排序法,大顶堆法)

Problem: 973. 最接近原点的 K 个点 文章目录 题目描述思路解题方法复杂度Code 题目描述 给定一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点&#xff0c;并且是一个整数 k &#xff0c;返回离原点 (0,0) 最近的 k 个点。 这里&#xff0c;平面…

获取网络ppt资源

背景&#xff1a; ​ 某度上有很多优质的PPT资源和文档资源&#xff0c;但是大多数需要付费才能获取。对于一些经济有限的用户来说&#xff0c;这无疑是个遗憾&#xff0c;因为我们更倾向于以免费的方式获取所需资源。 解决方案&#xff1a; ​ 然而&#xff0c;幸运的是&am…

python 笔记:将不同长度2D矩阵线性插值至相同长度(scipy.interpolate)

1 问题描述 我现在有三个2D矩阵&#xff0c;每一行是两个元素&#xff0c;代表经纬度&#xff1b;不同矩阵的行数不同 现在希望通过线性插补&#xff0c;使得每个2D矩阵行数相同 pth_lst[[[1,2],[1,3],[3,4]],[[3,4],[5,6],[7,8],[9,10],[11,12],[13,14],[15,16]],[[2,4],[5…

记录一次driud连接池的连接数用完问题

一、问题描述&#xff1a; 最直观的表现就是生产上项目崩了&#xff0c;无法访问。 二、分析原因&#xff1a; 通过查看生产日志&#xff0c;出现了大量的获取连接超时异常&#xff0c;具体如下&#xff1a; org.springframework.jdbc.CannotGetJdbcConnectionException:Fail…

Python 网络爬虫(四):初识网络爬虫

《Python入门核心技术》专栏总目录・点这里 文章目录 什么是爬虫爬虫的工作原理应用场景反爬虫合法和道德问题Robots 协议练习爬虫的一些网站总结 大家好&#xff0c;我是水滴~~ 在当今数字化时代&#xff0c;互联网上充斥着大量的数据和信息&#xff0c;而我们常常需要从这个…

vue运用之echart柱状图3D效果案例代码

前言 在ECharts中,创建3D柱状图需要使用GL模块,并设置type为’bar3D’ 柱状图案例可参考,我的这篇文章 Echarts之柱状图 3D柱状图的示例代码 // 引入ECharts主模块 var echarts = require(echarts); // 引入3D模块 var GL = require(echarts/util/graphic/GL

思维模型 移情效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。情感迁移&#xff0c;爱屋及乌。 1 移情效应的应用 1.1 移情效应在市场营销中应用-多芬&#xff08;Dove&#xff09;“真美运动” 多芬&#xff08;Dove&#xff09;是一家知名的个人护理…

软件工程 复习笔记

目录 概述 软件的定义&#xff0c;特点和分类 软件的定义 软件的特点 软件的分类 软件危机的定义和表现形式 软件危机 表现形式 软件危机的产生原因及解决途径 产生软件危机的原因 软件工程 概念 软件工程的研究内容和基本原理 内容 软件工程的基本原理 软件过程…

Redis使用Lua脚本

Lua脚本 redis可以支持lua脚本&#xff0c;可以使用lua脚本来将几个命令整合为一个整体来执行&#xff0c;这样可以使得多个命令原子操作&#xff0c;且可以减少网络开销 Lua的数据类型 Lua是一个动态类型的语言&#xff0c;一个变量可以存储任何类型的值&#xff0c;类型有&am…

27、卷积 - 卷积特征的可视化和一个神奇的网站

既然上一节说了卷积的本质是一个特征提取器,那么既然卷积神经网络在图像分类、图像检测、图像分割以及其他领域有这么好的表现,卷积算法到底提取了什么特征呢? 虽然有时候我们说神经网络是个黑盒,但是研究人员也一直在探索,如何将卷积学习到的特征给分析出来。 就是想要…

我们是如何让微服务在实践中“活色生香”的?

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容1. 前言2. 请求的路径分析3. 服务周期分析4. 请求格式转换5. 服务层设计6. 业务服务层设计7. 安全防护及策略8. 结论 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xf…

带你学C语言:带你学函数

目录 &#x1f30d;函数的概念 ★★☆☆☆库函数 ★★★★☆自定义函数 ★★★☆☆形参与实参 ★★★☆☆return语句 ★★★☆☆数组做函数参数 ★★★☆☆嵌套调用和链式访问 ★★★☆☆函数的声明和定义 ✍结束语 &#x1f30d;函数的概念 数学中我们其实就见过函数…

系统运维安全之病毒自检及防护

一、前言 Linux勒索病毒&#xff08;Linux ransomware&#xff09;是一种最令人恶心的计算机恶意病毒&#xff0c;它以侵入Linux系统&#xff0c;捆绑文件并要求支付赎金才能释放文件为主要目的&#xff0c;破坏用户的数据&#xff0c;造成数据讹诈。Linux勒索病毒它们的存在已…