经典算法回顾之最小生成树

article/2025/8/12 11:41:36

最小生成树(Minimum Spanning Tree,简称MST)是图论中的一个重要概念,主要用于解决加权无向图中连接所有顶点且总权重最小的树结构问题。本文对两种经典的算法即Prim算法和Kruskal算法进行回顾,并对后者的正确性给出简单的证明。

一. 定义

最小生成树是给定连通加权无向图 G = ( V , E ) G = (V, E) G=(V,E) 中的一棵生成树,满足以下条件:

  • 包含所有顶点:生成树覆盖了图中的所有顶点。
  • 边权之和最小:生成树中所有边的权重之和最小。
  • 无环性:生成树中不包含任何环路。
    形式化定义:假设 T T T E E E 的一个子集,且 ( V , T ) (V, T) (V,T) 是一棵树,使得 w ( T ) w(T) w(T)(即 T T T 中所有边的权重之和)最小,则 T T T G G G 的最小生成树。

二. 基本概念

  • :将图 G G G 的顶点集分为不相交的两部分,假设一个子集为 U U U, 则另一子集为 V − U V-U VU。如下图所示: U = { 0 , 4 , 5 } , V − U = { 1 , 2 , 3 , 6 } U=\{0,4,5\},V-U=\{1,2,3,6\} U={0,4,5},VU={1,2,3,6}
  • 跨边:穿过割的边称为跨边。如下图所示: ( 0 , 1 ) , ( 4 , 6 ) , ( 4 , 3 ) (0,1),(4,6),(4,3) (0,1),(4,6),(4,3) 为这一割的3条跨边。
    在这里插入图片描述

三. 基本性质

性质1: ( u , v ) (u,v) (u,v) 是某一割的最短跨边,那么必然存在一棵MST包含 ( u , v ) (u,v) (u,v)
推论1: ( u , v ) (u,v) (u,v) 不是任意割的最短跨边,那么任意MST均不包含它。
推论2:任意一棵MST也必然包含任意割的最短跨边 ( u , v ) (u,v) (u,v)

反证法证明性质1:
如下图所示,假设 ( u , v ) (u,v) (u,v) 未被任何MST采用,那么任取一棵MST,连接 ( u , v ) (u,v) (u,v) 必然会构成唯一的回路,且该回路必然包含 ( u , v ) (u,v) (u,v) 以及另一条跨边 ( s , t ) (s,t) (s,t)。 此时,若断开 ( s , t ) (s,t) (s,t), 那么我们将得到一棵新的生成树,且权重之和更小,于是我们得到了更小的MST。 这与假设不符,因此,若 ( u , v ) (u,v) (u,v) 是某一割的最短跨边,那么必然存在一棵MST包含 ( u , v ) (u,v) (u,v)

在这里插入图片描述

四. Prim算法

4.1 Prim算法描述
T 1 = ( { v 1 } ; ∅ ) T_1 = (\{v_1\}; \varnothing) T1=({v1};) 开始,逐步构造 T 2 、 T 3 、 . . . 、 T n T_2、T_3、...、T_n T2T3...Tn。其中, v 1 v_1 v1 可以任选。
T k = ( V k ; E k ) , ∣ V k ∣ = k , ∣ E k ∣ = k − 1 , V k ⊂ V k + 1 T_k = (V_k; E_k),|V_k| = k,|E_k| = k-1,V_k \subset V_{k+1} Tk=(Vk;Ek)Vk=kEk=k1VkVk+1
由以上分析,为由 T k T_k Tk 构造 T k + 1 T_{k+1} Tk+1
只需将 ( V k : V − V k ) (V_k : V-V_k) (Vk:VVk) 视作原图的一个割, 并在该割的所有跨边中,
找出极短者 e k = ( v k , u k ) e_k = (v_k, u_k ) ek=(vk,uk)
T k + 1 = ( V k + 1 ; E k + 1 ) = ( V k ∪ u k ; E k ∪ e k ) T_{k+1} = (V_{k+1}; E_{k+1}) = (V_k \cup {u_k}; E_k \cup {e_k} ) Tk+1=(Vk+1;Ek+1)=(Vkuk;Ekek)

4.2 Prim算法示例
在这里插入图片描述
4.3 Prim算法代码

#define INF 32767	                        //INF表示∞, 邻接矩阵中不可达边的权重为∞
int dis[1000], inMST[1000], mst = 0;        //dis: V-U 到 U的最短距离 mst: 最小生成树的权重和void  Prim(vector<vector<int>>& G, int v){inMST[v] = 1;for (int i = 0; i < G.size(); i++)  dis[i] = G[v][i];for (int i = 1; i < G.size(); i++) {	//循环n-1次int min = INF, k;for (int j = 0; j < G.size(); j++) 	//在(V-U)中找出离U最近的顶点kif (inMST[j] == 0 && dis[j] < min)min = dis[j], k = j;        // min: 最短距离,k:最近顶点编号mst += min;inMST[k] = 1;                       //标记k已经被加入for (int j = 0; j < G.size(); j++)  //更新其它点到U的距离if (inMST[j] == 0 && G[k][j] < dis[j])dis[j] = G[k][j];}
}

五. Kruskal算法

5.1 算法描述

  1. 将图 G = ( V , E ) G = (V, E) G=(V,E) 中的所有边按权重非降序排序。设排序后的边序列为 e 1 , e 2 , . . . , e ∣ E ∣ e_1, e_2, ..., e_{|E|} e1,e2,...,eE
  2. 初始化一个空的边集 T = { } T = \{\} T={},用于存放 MST 的边。
  3. 按排序后的顺序 ( i = 1 i = 1 i=1 ∣ E ∣ |E| E),依次考虑每条边 e i e_i ei
    • 如果将 e i e_i ei 加入 T T T 后, T T T 中不会形成环,则将 e i ei ei 加入 T T T ( T = T ∪ { e i } T = T ∪ \{e_i\} T=T{ei})。
    • 否则(即加入 e i e_i ei 会形成环),丢弃 e i e_i ei
  4. T T T 中包含 ∣ V ∣ − 1 |V| - 1 V1 条边时,算法终止。 T T T 即为所求的 MST。

5.2 Kruskal算法示例
在这里插入图片描述

5.3 Kruskal算法代码

int  parent[maxn], mst_w; 
struct Edge { int from, to, w; } edges[maxn];
bool compare(Edge e1, Edge e2) { return e1.w <= e2.w; }int Find(int  x) { 			//查找结点x的根(集合代表) if (x == parent[x])    	//找到根return x;	       	//返回根(集合代表) elsereturn parent[x] = Find(parent[x]);   //递归向上找根
}void Union(int x,int y) {    //合并x和y所在的集合int px = Find(x);int py = Find(y);if (px != py)            //如果两个集合不同则合并        parent[px] = py;
}int Kruskal(int en) { for (int i = 0; i < maxn; i++) parent[i] = i; //初始化并查集sort(edges, edges + en, compare);   for (int i = 0; i < en; i++) {int p1 = Find(edges[i].from);int p2 = Find(edges[i].to);if (p1 != p2) {Union(p1, p2);mst_w += edges[i].w;}}return mst_w;
}

5.4 Kruskal算法正确性证明
接下来,简单证明一下这个算法的正确性。本文的证明方式跟绝不多数书上和网上的证明方式不同,证明主要基于上面提到的一个性质和两个推论:

性质1: ( u , v ) (u,v) (u,v) 是某一割的最短跨边,那么必然存在一棵MST包含 ( u , v ) (u,v) (u,v)
推论1: ( u , v ) (u,v) (u,v) 不是任意割的最短跨边,那么任意MST均不包含它。(性质1的逆否命题)
推论2:任意一棵MST也必然包含任意割的最短跨边 ( u , v ) (u,v) (u,v)

为了证明Kruskal算法的正确性,需要证明两点:

  1. 因为构成了回路而放弃的边不属于任意MST。
  2. 按照边长顺序加入的边必然都属于某棵MST。

1)首先证明:因为构成了回路而放弃的边不属于任意MST。
首先,如果一个图中的所有边的权重都不相同时,这个图对应的MST是唯一的。这一点不难证明,因为在Prim算法中,每一步都只能选择唯一的一条最短跨边。但图中确实可能存在权重相同的边,处理方式也很简单,就是对边的权重做稍微扰动(针对某条边,扰动后要保证,权重原本比它小的边扰动后依然比它小,权重原本比它大的边扰动后依然比它大,即不改变相对顺序),使之各不相同,那么MST也是唯一的。

这里我们就假设给定图 G = ( V , E ) G = (V, E) G=(V,E) 的所有边的权重均不相同。
在Kruskal算法中,因为构成了回路而放弃的一些边。 我们说这些边不属于MST。
如下图所示,假设在Kruskal算法的某一步,我们将边 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 加入之后,在局部构成了回路。
在这里插入图片描述
在这个回路中(上图红色的边), e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 是最后加入的边,因此它是这个回路中权重最大的边。
进一步,如果将 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 作为一个跨边 (即将 v i v_i vi v j v_j vj 放到两个不相交的子集中),我们发现这个割无论如何都会穿过这个回路中的其它边。
又因为其他边的权重都大于 e k e_k ek, 因此 e k e_k ek 是不是任意割的最短跨边。
所以 e k e_k ek 不包含在MST中。

2)然后证明:按照边长顺序加入的边必然都属于某棵MST。
不失一般性,如下图所示,我们仍然假设接下来考查的是 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj)。它是否在MST中呢? 只要它在某一割的最短跨边,那么它就在MST中。
在这里插入图片描述
我们要将 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 构造为一个最短跨边,就要构造一个割,且这一割不经过别的更短的跨边(也就是不穿过那些已经在MST中的跨边)。为此,我么可以将那些已经在MST子树中的点缩点即可,这样割就不会穿过比 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 更短的跨边了。

如下图所示,缩点后,图中仅剩3个点,我们可以很容易的构造一个割将 v i v_i vi v j v_j vj 放到两个不相交的子集中。且 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj) 是该割的最短跨边,尽管可能还有其它跨边,但是Kruskal算法是按顺序遍历的边,所有还未遍历到的边的权重必然大于 e k = ( v i , v j ) e_k=(v_i,v_j) ek=(vi,vj)
在这里插入图片描述
为此,Kruskal算法的正确性得证。

六. Prim算法 和 Kruskal算法的局限性

我们一般讲最小生成树,只针对无向图,对于有向图是有问题的,如下图所示。
在这里插入图片描述
有向图的最小生成树->最小树形图: https://oi-wiki.org/graph/dmst/

参考资料

邓俊辉,数据结构(C++语言版),清华大学出版社


http://www.hkcw.cn/article/NZytHAJeSb.shtml

相关文章

Java八股文智能体——Agent提示词(Prompt)

这个智能体能够为正在学习Java八股文的同学提供切实帮助&#xff1a;不仅可以帮你优化答案表述&#xff0c;还能直接解答八股文相关问题——它会以面试者的视角&#xff0c;给出贴合求职场景的专业回答。 将以下内容发送给任何一个LLM&#xff0c;他会按照你提示词的内容&…

VScode编译调试debug,gpu的cuda程序,Nsight

进行下面操作的前提是&#xff0c;我们的环境已经能跑简单的CUDA程序了。 一、安装Nsight 二、创建launch.json文件 {"version": "0.2.0","configurations": [{"name": "CUDA C: Launch","type": "cuda-gdb…

飞牛fnNAS存储空间模式详解

目录 一、NAS的存储空间 二、多硬盘对NAS速度的提升原理 三、多硬盘对数据安全的提升原理 四、多硬盘对容量的提升原理 五、磁盘阵列模式 六、飞牛NAS支持的存储模式 七、具体如何选择存储空间模式 在数字化时代,数据是个人和企业发展的核心资产,但面临硬盘损坏、病毒…

vue3: baidusubway using typescript

项目结构&#xff1a; <!--npm install -D tailwindcss-3d BaiduSubwayMap.vue npm install -D tailwindcss postcss autoprefixer--> <template><div class"relative w-full h-screen"><!-- 地图容器 --><div id"subway-container…

【递归、搜索与回溯】专题二、二叉树中的深搜

文章目录 1.计算布尔二叉树的值1.1 题目1.2 思路1.3 代码 2.求根节点到叶节点数字之和2.1 题目2.2 思路2.3 代码 3.二叉树剪枝3.1 题目3.2 思路3.3 代码 4.验证二叉搜索树4.1 题目4.2 思路4.3 代码 5.二叉搜索树中第K小的元素5.1 题目5.2 思路5.3 代码 6.二叉树的所有路径6.1 题…

Windows商店中的免费扫雷游戏应用

《扫雷》是一款经典的单人益智小游戏&#xff0c;1992年微软发布的Windows 3.1中加入该游戏&#xff0c;从此风靡全世界。游戏目标是通过逻辑推理&#xff0c;在最短的时间内根据点击格子出现的数字找出所有非雷格子&#xff0c;同时避免踩雷。 此Windows应用实现了经典扫雷的…

无法运用pytorch环境、改环境路径、隔离环境

一.未建虚拟环境时 1.创建新项目后&#xff0c;直接运行是这样的。 2.设置中Virtualenv找不到pytorch环境&#xff1f;因为此时没有创建新虚拟环境。 3.选择conda环境&#xff08;全局环境&#xff09;时&#xff0c;是可以下载环境的。 运行结果如下&#xff1a; 是全局环境…

古老的传说(Player、Stage)是否还能在蓝桥云课ROS中重现-250601(失败)

古老的传说是否还能在蓝桥云课ROS中重现-250601 经典复现何其难&#xff0c;百分之二就凉凉&#xff01; 古老的传说 那是很久很久以前的故事……上个世纪的一个机器人项目 Player、Stage这个项目最早起源于1999年&#xff0c;由美国南加州大学机器人研究实验室开发&#xff0…

机器学习:逻辑回归与混淆矩阵

本文目录&#xff1a; 一、逻辑回归Logistic Regression二、混淆矩阵&#xff08;一&#xff09;精确率precision&#xff08;二&#xff09;召回率recall&#xff08;三&#xff09;F1-score&#xff1a;了解评估方向的综合预测能力&#xff08;四&#xff09;Roc曲线&#xf…

Spring是如何实现属性占位符解析

Spring属性占位符解析 核心实现思路1️⃣ 定义占位符处理器类2️⃣ 处理 BeanDefinition 中的属性3️⃣ 替换具体的占位符4️⃣ 加载配置文件5️⃣ Getter / Setter 方法 源码见&#xff1a;mini-spring 在使用 Spring 框架开发过程中&#xff0c;为了实现配置的灵活性&#xf…

继承与多态

继承与多态的分析 继承继承与访问限定比较派生类和基类关系派生类的构造顺序基类对象&#xff08;指针&#xff09;派生类对象&#xff08;指针&#xff09;的转换重载和隐藏 虚函数静态绑定与动态绑定指针调用其他调用的绑定方式虚函数实现的依赖 多态 继承 继承的本质&#…

API异常信息如何实时发送到钉钉

#背景 对于一些重要的API&#xff0c;开发人员会非常关注API有没有报错&#xff0c;为了方便开发人员第一时间获取错误信息&#xff0c;我们可以使用插件来将API报错实时发送到钉钉群。 接下来我们就来实操如何实现 #准备工作 #创建钉钉群 如果已有钉钉群&#xff0c;可以跳…

Amazon GameLift实战指南:低成本构建高并发全球游戏服务器架构

一、为什么游戏服务器需要GameLift&#xff1f; 行业痛点 传统自建服务器&#xff1a;扩容慢、DDoS防御弱、全球延迟不均 开源解决方案&#xff08;如Agones&#xff09;&#xff1a;运维成本高、需K8s深度知识 云虚拟机手动扩缩容&#xff1a;响应延迟导致玩家流失 GameLi…

2025安装与配置archlinux很详细

不知不觉&#xff0c;距离上次安装archlinux已经2年多了。我又打算把archlinux作为主力机使用了。 以前也写过一些类似的文章&#xff0c;有一些不变的内容&#xff0c;我直接从原来的文章中复制了&#xff08;包括截图&#xff09;。 《2021年vmware安装archlinux》 https:/…

字节golang后端二面

前端接口使用restful格式&#xff0c;post与get的区别是什么&#xff1f; HTTP网络返回的状态码有哪些&#xff1f; go语言切片与数组的区别是什么&#xff1f; MySQL实现并发安全避免两个事务同时对一个记录写操作的手段有哪些&#xff1f; 如何实现业务的幂等性&#xff08;在…

MyBatis03——SpringBoot整合MyBatis

目录 一、springboot整合mybatis 二、搭建环境 1、引入jar包 2、配置文件 3、准备控制层、业务层、持久层 4、SQLMapper文件 ​编辑 三、动态sql 四、分页 4.1逻辑分页 4.2物理分页 4.2.1引入分页插件在pom.xml 4.2.2使用分页插件 五、事务 编程式事务 声明式事…

【linux】知识梳理

操作系统的分类 1. 桌⾯操作系统: Windows/macOS/Linux 2. 移动端操作系统: Android(安卓)/iOS(苹果) 3. 服务器操作系统: Linux/Windows Server 4. 嵌⼊式操作系统: Android(底层是 Linux) Liunx介绍 liunx系统:服务器端最常见的操作系统类型 发行版:Centos和Ubuntu 远程连接操…

计算机网络第1章(上):网络组成与三种交换方式全解析

目录 一、计算机网络的概念二、计算机网络的组成和功能2.1 计算机网络的组成2.2 计算机网络的功能 三、电路交换、报文交换、分组交换3.1 电路交换&#xff08;Circuit Switching&#xff09;3.2 报文交换&#xff08;Message Switching&#xff09;3.3 分组交换&#xff08;Pa…

经典面试题:一文了解常见的缓存问题

在面试过程中&#xff0c;面试官的桌子上摆放着很多高频的面试题&#xff0c;能否顺利回答决定了你面试通过的概率。其中缓存问题就是其中的一份&#xff0c;可以说掌握缓存问题及解决方法是面试前必须准备的内容。那么缓存有什么典型的问题&#xff0c;出现的原因是什么&#…

Python Turtle实战:打造高精度图形化秒表

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…