第三部分 图论 - 第2章 最小生成树

定义

首先我们先了解下生成树的定义:

无向图中,一个连通图的最小连通子图称作该图的生成树(不能带环,保持连通,但边要尽可能的少)。
有n个顶点的连通图的生成树有n个顶点和n-1条边

那么最小生成树和生成树有什么关系呢?

这里的最小其实是指的边的权值之和最小,当然是要在保证它是生成树的前提下权值之和最小。
所以,对于一个连通图来说,在它的所有的生成树里面,边的权值之和最小的生成树就是该连通图的最小生成树,当然最小生成树也可以有多个,因为边的权值是可以相等的。

构建最小生成树

一般来说在无向图(有向图还不太一样)中构建最小生成树有两种算法:

Prim算法

算法流程如下:

        Prim算法(普里姆算法)一般是从图中一个点出发,让后找到与他相邻且点到集合边权最小,去联通,如此重复直到覆盖整个图为止。需要注意的是更新顺序,如果统计边权的话,需要先统计,在更新,不然在某种情况下更新会出错(如有负环)

        prim判断是否可以构成一颗最小生成树的方法是如果找到的最小点为我当前声明的最大值,则表明他与其他点不连通,自然构不成一颗最小生成树。

        当然他的时间复杂度也不是很优秀,O(n^2),但是我们可以用堆优化查找最小值那一步,但是这样做有些复杂,这里不推荐。如果遇到稀疏图就用Kruskal算法,稠密图用Prim朴素版就好了。

模板代码如下:

#include<bits/stdc++.h>
using namespace std;const int N = 1e3+10,INF = 0x3f3f3f3f;int n,m; 
int g[N][N],dist[N];
bool st[N];int prim(){memset(dist,0x3f,sizeof dist);int res = 0;for(int i = 0; i < n; i++){int t = -1;for(int j = 1; j <= n; j++){if(!st[j] && (t == -1 || dist[t] > dist[j])) t= j;}//图是不连通的,因为当前的点到集合的距离为正无穷,所以直接返回最小生成树不存在if(i && dist[t] == INF) return INF; if(i) res += dist[t];//因为最小生成树是看当前的点到集合的距离而不是源点距离所以是min(dist[j],g[t][j])比较for(int j = 1; j <= n; j++) dist[j] = min(dist[j],g[t][j]);st[t] = true;}return res;
}int main()
{cin >> n >> m;memset(g,0x3f,sizeof g);while(m--){int a,b,c; cin >> a >>b >> c;g[a][b] = g[b][a] = min(g[a][b],c); //重边保留较小的一条}int t = prim();if(t == INF) cout << "impossible" << endl;else cout << t << endl;return 0;
}

Kruskal算法

        Kruskal(克鲁斯卡尔)算法,他是先进行排序,让后在从小到大去选择,如果当前元素a和b已经连通就不去管他,如果不连通就直接连通,并且把权值放入总值中。也是很好实现。因为从小到大排序,所以说每次选择一定是最优。

算法流程:

        证明:

        在这里我们如果不选这条边,那么最后的生成树一定包含一条不小于这条边的边,那么我们把这条边替换上去,结果不会变差,可证这是最优解。

#include<bits/stdc++.h>
using namespace std;const int N = 1e6+10;int n,m,res,cnt; 
int p[N];struct Node{int a,b,w;
} a[N];bool cmp(Node a, Node b){return a.w < b.w;
}int find(int x){if(p[x] != x) p[x] = find(p[x]);return p[x];
}int main(){cin >> n >> m;for(int i = 1; i <= n; i++) p[i] = i;for(int i = 0; i < m; i++){int aa,b,w;cin >> aa >> b >> w;a[i] = {aa,b,w};}sort(a,a+m,cmp);for(int i = 0; i < m; i++){int aa = a[i].a,b = a[i].b,w = a[i].w;aa = find(aa),b = find(b);if(aa != b){  //判断是否连通 p[aa] = b;res += w;cnt++;}}if(cnt < n-1) cout << "impossible" << endl; //判断是否能组成最小生成树 else cout << res << endl;return 0;   
}

这两种算法各有优缺,需要根据题目合理运用。

稠密图用Prim,稀疏图用kruskal

例题引入

最短网络

思路分析

        看到要求连通整幅图那么就想到用最小生成树,并且是邻接矩阵读入的而且数据范围也不大,所以明显用Prim算法模板皆可以过。

代码

#include<bits/stdc++.h>
using namespace std;const int N = 1e3+10;
int g[N][N],dist[N];
bool st[N];
int n;int prim(){int res = 0;memset(dist,0x3f,sizeof dist);for(int i = 0; i < n; i++){int t = -1;for(int j = 1; j <= n; j++){if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;}if(i) res += dist[t];st[t] = 1;for(int j = 1; j <= n; j++) dist[j] = min(dist[j],g[t][j]);}return res;
}int main(){cin >> n;for(int i = 1; i <= n; i++){for(int j = 1; j <= n; j++) cin >> g[i][j];}cout << prim() << endl;return 0;
}

局域网

思路分析

        这个让求的是去掉网线的最大值,转换一下是不是让保留下来的网线最小?所以我们只需要对网线求最小生成树,从整体中去掉这些最小生成树上得网线,即使去掉网线的最大值。所以我们考虑些最短路径,用Kruskal算法(这个算法更加灵活,所以一般不是特殊输入就用这个),那么我们只需要在两个点连通的时候计入答案里(应为我们再求最小生成树的时候是不连通的时候累加,正好相反)。

代码

#include<bits/stdc++.h>
using namespace std;const int N = 1e6+10;
struct Node{int a,b,w;
} a[N];int p[N];
int n,m,res;bool cmp(Node a,Node b){return a.w < b.w;
}int find(int x){if(p[x] != x) p[x] = find(p[x]);return p[x];
}int main(){cin >> n >> m;for(int i = 1; i <= n; i++) p[i] = i;for(int i = 1; i <= m; i++) cin >> a[i].a >> a[i].b >> a[i].w;sort(a+1,a+m+1,cmp);for(int i = 1; i <= m; i++){int aa = a[i].a,b = a[i].b,w = a[i].w;aa = find(aa),b = find(b);if(aa != b){p[aa] = b;}else res += w; //在不连通的时候统计答案 }cout << res << endl;return 0;
}

繁忙都市

思路分析

        这道题的题意可能会有一点小饶,但是不要慌,翻译一下:就是至少要几条路可以保证城市连通,然后让你求上文这几条路中最大的一条。第一问很简单,我们都直到最小生成树是n-1条边,输出n-1即可,而第二问也不难,其实求得是最小生成树中最大的边。直接模板kruskal即可

代码

#include<bits/stdc++.h>
using namespace std;const int N = 1e5+10;
struct Node{int a,b,w;
} a[N];
int p[N];
int n,m,ans = -1e9;bool cmp(Node a,Node b){return a.w < b.w;
}int find(int x){if(p[x] != x) p[x] = find(p[x]);return p[x];
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++) p[i] = i;for(int i = 1; i <= m; i++) cin >> a[i].a >> a[i].b >> a[i].w;sort(a+1,a+m+1,cmp);for(int i = 1; i <= m; i++){int aa = find(a[i].a), b = find(a[i].b),w = find(a[i].w);if(aa != b){p[aa] = b;ans = max(ans,a[i].w); //统计最大那条路 }}cout << n-1 << " " << ans << endl; //n-1为最小生成树的边数,并且要n-1条边才可以使得城市连接 return 0;
}

联络员

思路分析

        这道题的题意也非常恶习呀!!好像最小生成树的题的题意就没有不恶心的。其实直白点说就是在最小生成树中有几条必选边,也有不必选边,那我们也是一眼就看出解法了吧?

        其实用灵活的Kruskal算法,提前把必选边加入集合,让后再对非必选边求最小生成树既可以。

代码

#include<bits/stdc++.h>
using namespace std;const int N = 1e6+10;
struct Node{int a,b,w;
}a[N];
int p[N];
int res,n,m;bool cmp(Node a,Node b){return a.w < b.w;
}int find(int x){if(x != p[x]) p[x] = find(p[x]);return p[x];
}int main(){cin >> n >> m;for(int i = 1; i <= n; i++) p[i] = i;for(int i = 1; i <= m; i++){int x,aa,bb,ww;cin >> x >> aa >> bb >> ww;if(x == 1){//把必选边提前加入集合,累加贡献值 res += ww;aa = find(aa),bb = find(bb);if(aa != bb) p[aa] = bb;}else{a[i] = {aa,bb,ww};  //非必选边存下,求最小生成树 }}sort(a+1,a+1+m,cmp);//模板 for(int i = 1; i <= m; i++){int aa = find(a[i].a),bb = find(a[i].b),ww = a[i].w;if(aa != bb){p[aa] = bb;res += ww;}}cout << res << endl;return 0;
}

连接格点

思路分析

        这道题一看不是跟上一道一样吗?

        是的,其实差不多,但是这道题有个不当人的地方那就是,输入很难很难处理,这里我们使用一个值代表了(x,y)坐标对,因为最最小生成树一般都是a->b权值是w,统一格式,然后看一下数据范围在结合上题我们用Kruskal算法,但是他的排序还是有点慢。

        所以我们想了方法,因为只有两个值1,2所以我们只需要先存纵向即可。

        处理完这些,我们只需要把已有的边提前加入集合,剩下的正常跑最小生成树

代码

#include<bits/stdc++.h>
using namespace std;const int N = 1010,M = N*N,K = 2*N*N;
int n,m; 
int ids[N][N]; //存储二位映射到一维的值 
struct Node{int a,b,w;
}e[K];
int p[M];
int k;int find(int x){if(p[x] != x) p[x] = find(p[x]);return p[x];
}//处理读入 
void get_edges(){int dx[4] = {-1,0,1,0},dy[4] = {0,1,0,-1},dw[4] = {1,2,1,2}; //四个方向和值 for(int z = 0; z < 2; z++){ //判断先存的横纵顺序 for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){for(int u = 0; u < 4; u++){ //遍历四个方向 if(u %2 == z){int x = i+dx[u],y = j+dy[u],w = dw[u];if(x && x <= n && y && y <= m){ //判断是否越界 int a = ids[i][j],b = ids[x][y]; if(a < b) e[k++] = {a,b,w};    //判断先后顺序因为为了避免重复更新             }}}} }}
}int main(){cin >> n >> m;for(int i = 1; i <= n*m; i++) p[i] = i;//构建二维映射到一维的值 for(int i = 1,t = 1; i <= n; i++){for(int j = 1; j <= m; j++,t++){ids[i][j] = t;}}int x1,x2,y1,y2;while(cin >> x1 >> y1 >> x2 >> y2){int a = ids[x1][y1],b = ids[x2][y2];p[find(a)] = find(b); //把已有的边存储进来集合 }int res = 0;get_edges();//正常跑最小生成树 for(int i = 0; i < k; i++){int a = find(e[i].a),b = find(e[i].b),w = e[i].w;if(a != b){p[a] = b;res += w;}}cout << res << endl;return 0;
}

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

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

相关文章

决策树 和 集成学习、随机森林

决策树是非参数学习算法&#xff0c;可以解决分类问题&#xff0c;天然可以解决多分类问题&#xff08;不同于逻辑回归或者SVM&#xff0c;需要通过OVR&#xff0c;OVO的方法&#xff09;&#xff0c;也可以解决回归问题&#xff0c;甚至是多输出任务&#xff0c;并且决策树有非…

国内NAT服务器docker方式搭建rustdesk服务

前言 如果遇到10054,就不要设置id服务器!!! 由于遇到大带宽,但是又贵,所以就NAT的啦,但是只有ipv4共享和一个ipv6,带宽50MB(活动免费会升130MB~) https://bigchick.xyz/aff.php?aff322 月付-5 循环 &#xff1a;CM-CQ-Monthly-5 年付-60循环&#xff1a;CM-CQ-Annually-60官方…

数据结构第三讲:单链表的实现

数据结构第三讲&#xff1a;单链表的实现 1.什么是单链表2. 节点3.单链表的实现3.1节点的结构3.2打印单链表3.3申请一个新节点3.4单链表尾部插入3.5单链表头部插入3.6单链表的尾部删除3.7单链表头部删除3.8查找3.9在指定位置之前插入数据3.10在指定位置之后插入数据3.11删除pos…

VS+opencv+环境配置

下载opencv库。 版本 - OpenCV 下载完了是一个exe文件&#xff0c;&#xff08;可以更换目录&#xff09;直接双击&#xff0c;也就是压缩。 vs配置&#xff1a; 调试-调试属性 点编辑&#xff0c;加入这两个&#xff0c;路径根据自己的opencv库 3、链接器 测试&#xff1a;…

在Postman中引用JS库

前言 在做接口测试时&#xff0c;出于安全因素&#xff0c;请求参数需要做加密或者加上签名才能正常请求&#xff0c;例如&#xff1a;根据填写的请求参数进行hash计算进行签名。postman作为主流的接口调试工具也是支持请求预处理的&#xff0c;即在请求前使用JavaScript脚本对…

Redis:AOF持久化

1. 简介 以日志的形式来记录每个写操作&#xff0c;将redis执行的每个写操作记录下来&#xff08;读操作不记录&#xff09;&#xff0c;只需追加文件但不可以改写文件&#xff0c;redis启动之初会重新构建数据&#xff0c;即redis重启后会将日志中的所有写指令重新执行一遍以达…

扰动观测器DOB设计及其MATLAB/Simulink实现

扰动观测器(Disturbance Observer, DOB)是一种在控制系统中用于估计和补偿未知扰动的重要工具,以增强系统的鲁棒性和稳定性。其设计过程涉及系统建模、观测器结构设计以及控制律的调整。 扰动观测器设计原理 系统建模: 首先,需要建立被控对象的数学模型,明确系统的状态变…

2024第八届全国职工职业技能大赛“网络与信息安全管理员”赛项技术文件及任务书

2024第八届全国职工职业技能大赛“网络与信息安全管理员”赛项技术文件及任务书 一、赛项概述&#xff1a;二、竞赛形式&#xff1a;三、竞赛规则四、竞赛样题4.1、第一场4.1.2、实操闯关赛4.2、第二场4.3、第三场 需要培训可以私信博主 欢迎交流学习&#xff01; [X] &#x1…

【深入理解SpringCloud微服务】深入理解nacos

【深入理解SpringCloud微服务】深入理解nacos Nacos服务注册内存注册表内存注册表的更新通知客户端服务变更、服务同步、健康检查2.x版本nacos的变化 Nacos服务注册 spring-cloud-alibaba-nacos-discovery通过实现spring-cloud-commons规范定义的接口&#xff0c;完成nacos接入…

昇思25天学习打卡营第11天|xiaoyushao

今天分享ResNet50迁移学习。 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;所以很少有人会从头开始训练整个网络。普遍的做法是&#xff0c;在一个非常大的基础数据集上训练得到一个预训练模型&#xff0c;然后使用该模型来初始化网络的权重参数或作为固定特征提…

论文阅读:Deep_Generic_Dynamic_Object_Detection_Based_on_Dynamic_Grid_Maps

目录 概要 Motivation 整体框架流程 技术细节 小结 不足 论文地址&#xff1a;Deep Generic Dynamic Object Detection Based on Dynamic Grid Maps | IEEE Conference Publication | IEEE Xplore 概要 该文章提出了一种基于动态网格图&#xff08;Dynamic Grid Maps&a…

操作系统面试知识点总结4

#来自ウルトラマンメビウス&#xff08;梦比优斯&#xff09; 1 文件系统基础 1.1 文件的相关概念 文件是以计算机硬盘为载体的存储在计算机上的信息集合&#xff0c;可以是文本文档、图片、程序。 文件的结构&#xff1a;数据项、记录、文件&#xff08;有结构文件、无结构式…

橙单前端项目下载编译遇到的问题与解决

今天下载orange-admin前端项目&#xff0c;不过下载下来运行也出现一些问题。 1、运行出现下面一堆错误&#xff0c;如下&#xff1a; 2、对于下面这个错误 error Expected linebreaks to be LF but found CRLF linebreak-style 这就是eslint的报错了&#xff0c;可能是原作者…

Python学习笔记44:游戏篇之外星人入侵(五)

前言 上一篇文章中&#xff0c;我们成功的设置好了游戏窗口的背景颜色&#xff0c;并且在窗口底部中间位置将飞船加载出来了。 今天&#xff0c;我们将通过代码让飞船移动。 移动飞船 想要移动飞船&#xff0c;先要明白飞船位置变化的本质是什么。 通过上一篇文章&#xff0…

新手小白的pytorch学习第十四弹------十一、十二、十三弹卷积神经网络CNN的习题

习题编号目录 No 1No 2No 3No 4No 5No 6No 7No 8No 9No 10No 11No 12No 13 练习题主要就是 写代码&#xff0c;所以这篇文章大部分是代码哟~ No 1 What are 3 areas in industry where computer vision is currently being used? No 2 工业异常检测&#xff0c;目标检测 Sea…

第三十四天 复合选择器之后代选择器

常用复合选择器包括 后代选择器、子选择器、并集选择器、伪类选择器 后代选择器 语法 选择器1 选择器2{属性:属性值;} 出现重复组可以用类名进行区别 后代选择器可以无限套娃 父子等级可以是人为创造的

利用GPT4o Captcha工具和AI技术全面识别验证码

利用GPT4o Captcha工具和AI技术全面识别验证码 &#x1f9e0;&#x1f680; 摘要 GPT4o Captcha工具是一款命令行工具&#xff0c;通过Python和Selenium测试各种类型的验证码&#xff0c;包括拼图、文本、复杂文本和reCAPTCHA&#xff0c;并使用OpenAI GPT-4帮助解决验证码问…

spring IOC DI -- IOC详解

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 4.2 Ioc 详解4.2.1 Bean的存储Controller(控制器存储)Service (服务存储)Repository(仓库存储)Component(组件存储)Configuration(配置存储) 4.2.2 为什么需要这么多类注解?4.2.3方法…

面试重点---快速排序

快排单趟 快速排序是我们面试中的重点&#xff0c;这个知识点也很抽象&#xff0c;需要我们很好的掌握&#xff0c;而且快速排序的代码也是非常重要&#xff0c;需要我们懂了还不行&#xff0c;必须要手撕代码&#xff0c;学的透彻。 在研究快速排序之前&#xff0c;我们首先…