前言

最近刚学了最小生成树,于是想趁热打火,先来总结一下~

前置芝士

  • 图、树的概念、遍历与存储
  • 并查集

本文章所有代码均以C++编写。

最小生成树的概念

最小生成树(Minimum Spanning Tree, MST)是一种特殊的图。它具备朴素树的所有性质,但也是一张图中边权最小但经过每个节点的子树。

定义

一个有 nnn 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 nnn 个结点,并且有保持图连通的最少的边。最小生成树可以用Kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。[1]

大意

在一张连通图 GGG 里,有 nnn 个节点和 mmm 条边,第 iii 条边的权值为 wiw_iwi​ 。我们设图 GGG 的最小生成树为 TTT 。

那么,图 GGG 的最小生成树 TTT 必须具备以下条件:

  • TTT 必须包括 GGG 的所有节点;
  • TTT 的边数必须等于 n−1n - 1n−1;
  • TTT 的边权和必须在所有生成树中最小;
  • TTT 必须为 GGG 的子图。

假设我们有如下的连通图 GGG(左),那么它的最小生成树 TTT 如图所示(右)。


[2]

显然对于图 GGG,TTT 的边权和在所有生成树中最小。我们称这样的树为图 GGG 的 最小生成树(简称MST)。

算法

我们不妨来介绍两种较为常见的求最小生成树的算法。

在介绍算法之前,我们先来看这样一道题目:

题目链接:洛谷P3366。

题目很容易理解,即求出给定图的最小生成树边权和。那么,我们来看看这些算法吧!

Kruskal

基本思路

Kruskal(克鲁斯卡尔) 是一种贪心策略,类似图论中的Bellman-Ford算法。

简单来说,如果我们挑选了 n−1n-1n−1 条较小边,那么显而易见,这 n−1n-1n−1 条边的权值相加也会是一个较小值。按照这种思路,我们可以挑选 n−1n-1n−1 条 GGG 里面最小的边并将它们相连。

但是,你以为这就完了?怎么可能。

显然我们上面的做法有一个缺陷:它虽然保证了边权和最小,但是得出的却并不一定是一棵树。相反,它反而有可能得出来一个图(或森林)。我们需要解决这个问题。

显然,对于一条 u↔vu \leftrightarrow vu↔v 的无向边,若点 uuu 和点 vvv 已经连通(直接或间接),那么我们就不再需要加入当前边了。

对于每次遍历,我们都会对当前边的所到达的节点进行一个排查:如果节点已经连通,则无需加边;否则连接两点。

这样一来,我们就剩下一个最重要的问题没解决了:如何判断两个点有没有连通?

我们可以使用并查集这个数据结构进行存储和判重。每次判断两点的连通性的时候,我们只需要查询他们的祖先是否相同即可。同理,对于每次连接操作,我们只需要进行并查集的合并操作来合并 u,vu,vu,v 两点即可。

参考代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2 * 1e5 + 10;struct edge {  // 存边int u, v, w;
} e[N];
edge mst[5010];  // 最小生成树
int vtx[5010], k, ans, n, m;  // vtx并查集数组,k当前最小生成树节点数,ans边权和bool cmp(edge a, edge b) {return a.w < b.w;  // 按照边权排序
}int Find(int x) {  // 并查集查找操作if (vtx[x] == x) return x;return vtx[x] = Find(vtx[x]);
}void Union(int u, int v) {  // 并查集合并操作int fu = Find(u), fv = Find(v);if (fu != fv) vtx[fv] = fu;
}void kruskal() {  // Kruskal最小生成树for (int i = 0; i < m; i++) {  // 遍历所有边if (Find(e[i].u) != Find(e[i].v)) {  // 如果两点没有连接k++;  // MST边数++mst[k].u = e[i].u, mst[k].v = e[i].v, mst[k].w = e[i].w;  // 记录当前边ans += e[i].w;  // 总权重增加Union(e[i].u, e[i].v);  // 连接两点} }
}int main() {scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++) vtx[i] = i;  // 并查集初始化,祖先都是自己,即每个点都未连接for (int i = 0; i < m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);sort(e, e + m, cmp);  // 对边权进行排序kruskal();  // 获取MSTif (k == n - 1) printf("%d\n", ans);  // 如果边数满足条件,输出总权值else printf("orz");  // 否则输出orzreturn 0;
}

Prim

基本思路

Prim(普利姆) 是Dijkstra的一个扩展。

Prim算法与Dijkstra算法唯一的区别在于:Prim算法所记录的距离(dis数组)并非从某个起点到终点的距离,而是当前的生成树到某个点的最短距离。

其余部分与Dijkstra算法一致。同样,Prim算法也可以使用堆进行优化,以提高效率。

但是,一般我们不推荐在稀疏图上使用Prim堆优化。堆优化后的Prim在稀疏图上的效率与Kruskal类似,但明显Kruskal代码复杂度较低。

参考代码

#include <iostream>
#include <cstdio>
#include <memory.h>
#include <algorithm>
using namespace std;
const int N = 5010;int g[N][N], dis[N];  // 邻接矩阵存图
bool vis[N];  // 是否经过了某个点
int n, m, ans, u, v, w;void initialize() {  // 初始化memset(g, 0x3f, sizeof(g));memset(dis, 0x3f, sizeof(dis));
}void addEdge(int u, int v, int w) {  // 添加一条 u <-> v,权值为w的无向边if (u != v && g[u][v] > w) g[u][v] = g[v][u] = w;
}void prim() {  // Primfor (int i = 0; i < n; i++) {  // 遍历每个点int k = 0;  // 距离最近的点的坐标for (int j = 1; j <= n; j++) {  // 寻找距离点i最近的未访问过的点if (!vis[j] && dis[j] < dis[k]) k = j;}vis[k] = 1;  // 标记访问ans += dis[k];  // 记录权值for (int j = 1; j <= n; j++) {  // 再次遍历每个点,更新最短路if (!vis[j] && dis[j] > g[k][j]) {  // 未访问过且路程比当前记录的小dis[j] = g[k][j];  // 更新权值}}}
}int main() {initialize();scanf("%d%d", &n, &m);while (m--) {scanf("%d%d%d", &u, &v, &w);addEdge(u, v, w); addEdge(v, u, w);}dis[1] = 0;  // 约定俗成,从点1跑prim,赋值为0是为了消除自环prim();for (int i = 1; i <= n; i++) {  // 判断是否建立成树if (!vis[i]) {  // 如果有点未访问,则没有最小生成树printf("orz\n");return 0;}}printf("%d\n", ans);  // 否则输出权重和return 0;
}

参考资料

  • 最小生成树_百度百科
  • 最小生成树的两种方法(Kruskal算法和Prim算法)

最小生成树MST详解相关推荐

  1. 最小生成树算法详解(prim+kruskal)

    最小生成树概念: 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里 ...

  2. Kruskal(克鲁斯卡尔) 最小生成树 算法详解+模板

    最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树. 例如,对于如上图G4所示的连通网可以有多棵权值总和 ...

  3. 最小生成树(详解普里姆算法)

    首先,我们来看一下图的一些基本知识点: 图:图 G=(V,E) 由顶点集 V 和边集 E 组成.每条边对应一个点对 (v,w),其中 v,w 属于 V .如果图中的点对是有序的,那么该图就是有向图,反 ...

  4. 普利姆算法(prim)求最小生成树(MST)过程详解

    生活中最小生成树的应用十分广泛,比如:要连通n个城市需要n-1条边线路,那么怎么样建设才能使工程造价最小呢?可以把线路的造价看成权值求这几个城市的连通图的最小生成树.求最小造价的过程也就转化成求最小生 ...

  5. 最小生成树-Prim算法详解(含全部代码)

    目录 适用条件 测试所用图 算法详解 Prim算法代码 全部代码 实验结果 适用条件 加权连通图 测试所用图 所用原图及生成过程 其中,(a) 为原图,圆圈里面是节点的名称,边上的数字是边的权值.由实 ...

  6. 数据结构--图(Graph)详解(四)

    数据结构–图(Graph)详解(四) 文章目录 数据结构--图(Graph)详解(四) 一.图中几个NB的算法 1.普里姆算法(Prim算法)求最小生成树 2.克鲁斯卡尔算法(Kruskal算法)求最 ...

  7. ORB-SLAM2代码/流程详解

    ORB-SLAM2代码详解 文章目录 ORB-SLAM2代码详解 1. ORB-SLAM2代码详解01_ORB-SLAM2代码运行流程 1 运行官方Demo 1.2. 阅读代码之前你应该知道的事情 1 ...

  8. 数据结构(C语言版) 第 六 章 图 知识梳理 + 习题详解

    目录 一. 图的基本定义和术语 一.图的基本概念 1.度 2.连通 (1)连通图 (2)强连通/强连通图 3.回路 4.完全图 二.图的三种存储结构 1.邻接矩阵表示法 2.邻接表(链式)表示法 3. ...

  9. halcon例程讲解_跟我学机器视觉-HALCON学习例程中文详解-开关引脚测量

    跟我学机器视觉-HALCON学习例程中文详解-开关引脚测量 This example program demonstrates the basic usage of a measure object. ...

  10. Linux-shell-完全详解

    Linux-shell-完全详解(1) 一. Shell简介:什么是Shell,Shell命令的两种执行方式1 二. 几种常见的Shell1 三. Shell脚本语言与编译型语言的差异2 四.什么时候 ...

最新文章

  1. mysql 5.6 uf8mb4_MySQL5.7升级到8.0过程详解
  2. 今年央视的春晚能给人带来惊喜吗?
  3. 实变函数与泛函分析导论
  4. 图说数据中心空调系统原理和架构
  5. 如何查找Power BI本地报表服务器产品密钥
  6. 论得失。。。技术方向
  7. LVS——DR模式下的健康检查
  8. 来,一起见识下全球海底光缆分布图
  9. 2091操作系统引论
  10. 编译Qtopia2.2.0
  11. uniapp 发布网站遇到的问题(跨域,nginx代理失败,index无法打开,手机端无法访问等)
  12. linux点亮桌面,教你如何点亮自己的Ubuntu 屏幕
  13. java gui 测试工具_开发者眼中最好的22款GUI测试工具(上)
  14. YoloV3 先验框
  15. 笔记本计算机风扇连线,机箱风扇电源怎么接线?机箱风扇接口知识及接法图解教程...
  16. c语言学习笔记(持续更新中)
  17. sonar入门:全网最全的概念解析与安装
  18. python画波浪线_PPT绘制波浪线的四种方法
  19. c语言杨辉三角的实验心得体会范文,大学生实验心得体会精选范文5篇
  20. 求1+2!+3!+...+N!的和

热门文章

  1. DSP2812之定时器0
  2. masm5安装教程_MASM使用方法及版本号
  3. (UE4 4.20)UE4 碰撞(Collision)之光线检测(RayTrace )
  4. Android开发之获取GPS位置案例源码详解
  5. python爬虫+谷歌翻译json字符串
  6. 消息钩子入门篇(4)---示例__外壳钩子(WH_SHELL)
  7. kindeditor使用方法
  8. 开源绘图工具drawio
  9. IO前哨站之##File##
  10. 教你如何把书本上的字快速弄到电脑上