最小生成树算法的解题思路与 C++ 算法应用

一、最小生成树算法针对问题类型及概述

先来简要陈述一下树的概念:一个由 N N N 个点和 N − 1 N-1 N1 条边组成的无向连通图。由此,我们可以得知生成树算法的概念:在一个 N N N 个点的图中找出一个由 N − 1 N-1 N1 条边组成的树。

具体来说,我们是在一个图 G ( N , M ) G(N,M) G(N,M) 中找到一个生成树 G ( N , N − 1 ) G(N,N-1) G(N,N1) ,在生成树 G ( N , N − 1 ) G(N,N-1) G(N,N1) 中的所有边 ∀ w ∈ G ( N , N − 1 ) \forall w \in G(N,N-1) wG(N,N1) 中,我们要不断调整我们找到的生成树,使得所有边权之和 ∑ x ∈ G ( N , N − 1 ) x \sum_{x \in G(N,N-1)} x xG(N,N1)x 最小。因此,我们得出了最小生成树的概念。

最小生成树算法一般有两种较为常用的算法,分别是 Prim 算法和 Kruscal 算法。 Prim 算法和最短路中的 Dijkstra1 算法十分相似,以为对象进行搜索,时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。 Kruscal 算法以为对象进行搜索,先将所有边进行排序,再一次抽取所需要的边组成最小生成树,时间复杂度约为 O ( m log ⁡ m ) O(m\log m) O(mlogm) 。由前面学习最短路算法的经验可知,由于 Prim 算法注重点的数量,时间效率与点的数量大致成正比,所以 Prim 算法适用于稠密图(点多边少);由于 Kruscal 算法注重边的数量,时间效率与边的数量大致成正比,所以 Kruscal 算法适用于稀疏图。

最小生成树一般可以用来解决在一个图中找出一个既包含所有点,又最小的结构问题;结合实际应用时,最小生成树算法可以有更多的用途。

二、最小生成树算法的过程

下面我们来介绍引言中提到的两种算法,Prim 算法和 Kruscal 算法。

1. Prim 算法的详细过程及主要思想

Prim 算法主要设置一个状态 d ( i ) d(i) d(i) 表示第 i i i 个点到最小生成树的距离。

为了更加具象化地展现 Prim 最小生成树算法,下面我将举如下的一个例子来说明该算法的原理。假设我们现在有一张图 G ( M , N ) G(M,N) G(M,N)

3
1
2
10
1
20
1
d = 0
2
d = inf
3
d = inf
4
d = inf

首先,我们会像 Dijkstra 算法一样,将 d d d 数组初始化为 ∞ \infin ,然后将 d ( 1 ) = 0 d(1)=0 d(1)=0 。注意,由于这里是最小生成树算法,所以使任一点作为计算的源点都是合理的操作。

接下来,我们围绕源点 1 1 1 更新其周边的点 i i i d ( i ) d(i) d(i) 值为 1 , i → \overrightarrow{1,i} 1,i

3
1
2
10
1
20
1
d = 0
2
d = 3
3
d = 1
4
d = 2

然后,我们捡取其中 d d d 值非零且最小的点,得到 3 3 3 号节点,因此将 3 3 3 号节点归入最小生成树中,并将 d ( 3 ) = 0 d(3)=0 d(3)=0 ,随后更新与 3 3 3 号节点相邻的所有节点的 d d d 值。注意,并不是所有节点都一定需要更新,而是与之前取得与最小生成树的距离的值去较大值。在这个实例中,我们需要更新 2 2 2 号节点。此时,最小生成树为 1 − 3 1-3 13

3
1
2
10
1
20
1
d = 0
2
d = 3
3
d = 0
4
d = 1

重复上面的步骤,选取此时的最小值 4 4 4 号点,相应地更新其周围的点。此时,最小生成树为 1 − 3 − 4 1-3-4 134

3
1
2
10
1
20
1
d = 0
2
d = 3
3
d = 0
4
d = 0

因此,我们的最小生成树为 1 − 3 − 4 − 2 1-3-4-2 1342 ,总代价为 1 + 2 + 1 = 4 1+2+1=4 1+2+1=4

更加抽象、准确的表述为: Prim 算法是一种贪心算法,先设置一个状态 d d d 表示节点与最小生成树的距离。再选定一个源点,更新该点和它周围的点的 d d d 值,将现在的 d d d 值作为初始值。在程序设计的时候,可以认为开始的时候所有点离最小生成树的距离均为 ∞ \infin ,然后通过第一次更新将他们重新赋值。赋值操作的状态转移方程如下:
d i = min ⁡ j → i { i , j ‾ } d_i=\min_{j \rightarrow i}\{\overline{i,j}\} di=jimin{i,j}
此处, j j j 为当前所在的点, i i i 为要更新的点, j → i j \rightarrow i ji 表示能从 j j j 出发到达 i i i ,即 i , j i,j i,j 之间连通。接下来,我们选择一个当前 d d d 值不为零且最小的点,继续重复上面的步骤,直到所有点的 d d d 值均为 0 0 0 ,算法结束。

统计答案时,设置一个变量 A A A ,当程序遍历到一个节点的时候,就在当前节点的 d d d 值被 0 0 0 覆盖之前把 d d d 值累加到 A A A 里面去,表示一个边权,最后的最小代价即为 A A A

Prim 算法的代码实现如下:

#include <bits/stdc++.h>using namespace std;const int N = 107;struct Node
{int to, wi;
};int n, m, d[N], v[N], ans;
vector<Node> e[N];
vector<int> q;int main()
{cin >> n >> m;for (int i = 1, a, b, c; i <= m; ++i){cin >> a >> b >> c;e[a].push_back({b, c});e[b].push_back({a, c});}memset(d, 0x3f, sizeof(d));d[1] = 0;for (int i = 1; i <= n; ++i){int minn = 1e9, p = 0;for (int j = 1; j <= n; ++j){if (d[j] < minn && !v[j]){minn = d[j];p = j;}}v[p] = 1;ans += d[p];for (Node j : e[p]){if (!v[j.to]){d[j.to] = min(d[j.to], j.wi);}}}cout << ans << endl;return 0;
}

2. Kruscal 算法的主要思想及实现

Kruscal 算法与 Prim 算法不同,使用边为对象进行搜索。Kruscal 通过先把每条边分别存储并进行排序,计算出每次代价较小的边。判断如果这条边的两个端点都不在树之内,则我们将这条边选入最小生成树。如果这条边的两个端点都在树之内,则说明不能加上这条边。证明如下:
如果我们在一棵树中再添加一条边,这个树将满足 G ( N , N − 1 ) → G ( N , N ) G(N,N-1) \rightarrow G(N,N) G(N,N1)G(N,N) ,从而与树的定义相违背。因此,我们不能再一棵树中再次添加一条边。
在判断每个节点是否属于一棵树的时候,我们应该使用并查集2,即每当加入一条新边的时候,将新加入的两个节点合并为一个集合。如果两个节点属于不同的集合,则合并两个集合,方法与普通并查集的方法相同,无方向、无边权。最后,我们只需统计每次选入并查集的边的边权之和。

具体操作如下:如果两个节点 i , j i,j i,j 的父节点不属于同一个集合,则将这条边 i , j ‾ \overline{i,j} i,j 加入最小生成树。反之,跳转到下一条边。

一个实例如下:
我们定义一张图如下:

3
1
2
10
3
6
1
2
3
4

现将所有边排序,得到边权的顺序为 { 1 , 2 , 3 , 3 , 6 , 10 } \{1,2,3,3,6,10\} {1,2,3,3,6,10}

接下来,我们按照 Kruscal 算法的顺序进行操作:

  • 选择第一条边 2 , 4 ‾ \overline{2,4} 2,4 ,边权为 1 1 1 ,加入最小生成树:
3
1
2
10
3
6
1
2
fa = 2
3
4
fa = 2
Cost
ans = 1
  • 选择第二条边 3 , 4 ‾ \overline{3,4} 3,4 ,边权为 2 2 2 ,加入最小生成树:
3
1
2
10
3
6
1
2
fa = 2
3
fa = 2
4
fa = 2
Cost
ans = 3
  • 选择第三条边 2 , 3 ‾ \overline{2,3} 2,3 ,但由于 f a ( 2 ) = f a ( 3 ) = 2 \mathrm{fa}(2)=\mathrm{fa}(3)=2 fa(2)=fa(3)=2 ,所以忽略这条边。

  • 选择第四条边 1 , 2 ‾ \overline{1,2} 1,2 ,边权为 3 3 3 ,加入最小生成树。

3
1
2
10
3
6
1
fa = 1
2
fa = 1
3
fa = 1
4
fa = 1
Cost
ans = 6

至此,我们已经完成了最小生成树的构建,结构为 1 − 2 − 4 − 3 1-2-4-3 1243 ,边权总和为 3 + 1 + 2 = 6 3+1+2=6 3+1+2=6

代码实现时,注意并查集的加入即可,具体实现方法如下。

#include <bits/stdc++.h>using namespace std;const int N = 307;struct Node
{int u, v, w;
} e[N * N];int n, m, fa[N], v[N], ans;int getfa(int x)
{if (fa[x] == x) return x;return fa[x] = getfa(fa[x]);
}int main()
{cin >> n >> m;for (int i = 1; i <= n; ++i){fa[i] = i;}for (int i = 1; i <= m; ++i){cin >> e[i].u >> e[i].v >> e[i].w;}sort(e + 1, e + m + 1, [&](Node a, Node b){return a.w < b.w;});int sel = 0;for (int i = 1; i <= m && sel < n - 1; ++i){int fx = getfa(e[i].u), fy = getfa(e[i].v);if (fx != fy){fa[fx] = fy;ans = max(ans, e[i].w);sel++;}}cout << n - 1 << " " << ans << endl;return 0;
} 

三、最小生成树算法的实际应用

1. 使用最小生成树算法实现在一个图中选择若干条边将整个图连通且代价最小

这其实本质上就是最小生成树算法。因为树是一个最小的连通图。证明如下:

用反证法。假设我们有一个图 G ( N , N − 2 ) G(N,N-2) G(N,N2) 为连通图,则我们可以知道在这个图中,任意两点间都有路径相连。因为这张图一共有 N − 2 N-2 N2 条边,由鸽巢原理,知最多可以放下 N − 1 N-1 N1 个节点,与条件中的 N N N 个节点相矛盾。由数学归纳法,知对于所有 1 ≤ M ≤ N − 2 1 \le M \le N-2 1MN2 G ( N , M ) G(N,M) G(N,M) 均不可能是连通图。

从这个推论,我们可以解决大部分的最小生成树问题,剖析题目中所给出的已知条件,一旦有形如“在一个图中选择若干条边将整个图连通且代价最小”的字眼,就一定是最小生成树算法。


  1. Dijkstra 算法的详细用途详见这篇文章。文章链接 ↩︎

  2. 并查集算法的详细用途详见这篇文章。文章链接 ↩︎

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

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

相关文章

feign.FeignException$NotFound: [404 ] during [POST] to [http://ti/ti/v1/i/se

feign.FeignException$NotFound: [404 ] during [POST] to [http://ti/ti/v1/i/send 原因&#xff1a;多个地方注册 FeignClient(name “ti”, path “/ti/v1/i/send/repeat”) 解决&#xff1a;删除一个即可

Mac m1 通过docker镜像安装kafka

kafka依赖zookeeper&#xff0c;因此需要使用docker同时安装zookeeper和kafka。 macOS的docker在容器和宿主之间无法通过ip直接通信&#xff0c;因此在安装的时候需要特殊注意与ip相关的设置。当容器需要访问宿主ip时&#xff0c;需要使用docker.for.mac.host.internal或者host…

01初始uni-app+tabBar+首页

初始uni-apptabBar首页 1. uni-app1.1 新建uni-app项目1.2 目录结构1.3 把项目配置运行到微信开发者工具 2. tabBar3.1 首页3.1 配置网络请求3.2 轮播图区域3.3 分类导航区域3.4 楼层区域 1. uni-app ​ uni-app 是使用 Vue.js 开发前端应用的框架。开发者编写一套代码&#x…

微信小程序,微信授权手机号码

uniapp中index.vue: <template><view class"content"><button open-type"getPhoneNumber" getphonenumber"getPhoneNumber"type"primary">授权手机号登录 </button></view></template><scrip…

数据结构 学习 图 2025年6月14日 12点57分

搜索算法 深度优先搜索 一种用于遍历或搜索树或图的算法。它沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。 DFS核心思想 深度优先&#xff1a;尽可能深地搜索树的分支 回溯思想&#xff1a;当节点v的所在边都已被探寻过&#xff0c;搜索将回溯到发现节点v的…

H3C路由器使用PBR 实现两条互联网专线互为备份

实验拓扑 图 1-1 注&#xff1a;如无特别说明&#xff0c;描述中的 R1 或 SW1 对应拓扑中设备名称末尾数字为 1 的设备&#xff0c;R2 或 SW2 对应拓扑中设备名称末尾数字为 2 的设备&#xff0c;以此类推&#xff1b;另外&#xff0c;同一网段中&#xff0c;IP 地址的主机位为…

深化信创生态布局!聚铭网络与海量数据完成产品兼容性互认证

近日&#xff0c;聚铭网络成功与海量数据完成了一系列产品的兼容性互认证&#xff0c;并获得了由海量数据颁发的产品兼容互认证书。这一成就标志着双方在技术整合方面迈出了重要一步。 经过全面严格的测试&#xff0c;聚铭网络自主研发的安全系列产品&#xff0c;包括聚铭下一…

Unity AR+ 百度AI 实现 物体识别与对应英文翻译

一、前言 我目前实现了拍照保存到手机的功能 我想进一步优化&#xff0c;实现通过手机摄像头实时识别眼前的物体&#xff0c;显示对应的英文的功能。 1.项目技术栈&#xff1a;Unity 2022.3.53 Vuforia 11 百度物体识别API 百度翻译API 2.功能目标&#xff1a;使用手机摄像…

Vue.js第二节

计算属性、事件绑定、条件判断、遍历循环 计算属性&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…

从开源代码入场无人机学术研究到商业化市场的全路径指南-优雅草卓伊凡

从开源代码入场无人机学术研究到商业化市场的全路径指南-优雅草卓伊凡 引言&#xff1a;开源代码在无人机研究中的重要性 优雅草卓伊凡在这里告诉大家&#xff0c;如果真的要开始进入无人机领域&#xff0c;我们需要一步步开始研究。目前先去看看开源无人机代码是尤为重要的&…

window11中开启ubuntu22.04子系统

一、启用Windows子系统 打开控制面板 选择程序然后点击“启用或关闭Windows功能” 勾选如下2项&#xff0c;点击确定 二、安装内核升级包 打开链接https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi下载内核升级包&#xff0c;打开后安装、重启电脑…

80Qt窗口_对话框

目录 5. 对话框 5.1 对话框介绍 用例1&#xff1a; 用例2&#xff1a; 用例3&#xff1a; 用例4&#xff1a; 5.2 对话框的分类 5.2.1 模态对话框 5.2.2 ⾮模态对话框 5. 对话框 5.1 对话框介绍 对话框是 GUI 程序中不可或缺的组成部分。⼀些不适合在主窗⼝实现的功…

Pyenv 跟 Conda 还有 Poetry 有什么区别?各有什么不同?

pyenv、Conda 和 Poetry 是 Python 生态中常用的工具&#xff0c;但它们的核心功能和用途不同&#xff0c;通常可以结合使用。以下是它们的区别和特点&#xff1a; 1. pyenv 用途&#xff1a;管理多个 Python 解释器版本。 核心功能&#xff1a; 安装不同版本的 Python&#x…

数学符号和标识中英文列表(含义与示例)

数学符号和标识的参考&#xff0c;涵盖了数学的各个主要分支&#xff0c;并提供清晰的定义和示例&#xff0c;方便快速查找和学习收藏。 目录 基础数学符号几何符号代数符号线性代数符号概率与统计符号集合论符号逻辑符号微积分与分析符号数字与字母符号 特点 中英对照&…

「Java流程控制」switch结构

知识点解析 1.switch结构的核心概念 switch语句是一种多分支选择结构,它根据表达式的值来选择执行不同的代码块。与if-else结构相比,switch更适合处理离散的、有限个值的比较。 2.switch结构的基本语法 switch (表达式) {case 值1:// 代码块1[break;]case 值2:// 代码块…

从0开始学习R语言--Day27--空间自相关

有的时候&#xff0c;我们在数据进行分组时&#xff0c;会发现用正常的聚类分析的方法和思维&#xff0c;分组的情况不是很理想。其实这是因为我们常常会忽略一个问题&#xff1a;假设我们正在分析的数据是真实的&#xff0c;那么它也肯定在一定程度上符合客观规律。而如果我们…

Excel将表格文件由宽数据转为长数据的方法

本文介绍基于Excel软件的Power Query模块&#xff0c;实现表格数据由宽数据转为长数据的具体方法。 长数据和宽数据是数据分析中的2种基本数据组织形式&#xff0c;二者在结构、用途、适用场景等方面各有特点。其中&#xff0c;宽数据 &#xff08;Wide Format&#xff09;以“…

SpringAI + DeepSeek大模型应用开发 - 入门篇

三、SpringAI Spring AILangChain4jChat支持支持Function支持支持RAG支持支持对话模型1515向量模型1015向量数据库1520多模态模型51JDK178 1. 对话机器人 1.1 快速入门 步骤①&#xff1a;引入依赖&#xff08;先去掉openai的starter依赖&#xff0c;因为要配置API_KEY&#…

ROS docker使用显卡驱动rviz gazebo,以及接入外设和雷达

ROS docker使用显卡驱动rviz gazebo&#xff0c;以及接入外设和雷达 由于我的电脑装ubuntu22.04系统&#xff0c;想使用ros noetic开发&#xff0c;使用鱼香ros一键安装docker安装。但是启动dockek中rviz无法使用显卡驱动&#xff0c;usb相机端口不显示&#xff0c;网口雷达无…

ruoyi后端框架的mapper层复杂字段数据获取问题

背景。如下是复杂字段。需要在mapper.java类注解中声明autoResultMap true才会进行处理。前提是&#xff0c;创建后端程序代码没有添加mapp.xml文件。故用注解简化代替。