当前位置: 首页 > news >正文

Prim 与 Kruskal 算法在最小生成树中的应用

欢迎来到我的算法探索博客,在这里,我将通过解析精选的LeetCode题目,与您分享深刻的解题思路、多元化的解决方案以及宝贵的实战经验,旨在帮助每一位读者提升编程技能,领略算法之美。
👉更多高频有趣LeetCode算法题
👉LeetCode高频面试题题单

最小生成树:用最少的代价连接整个世界

掌握了MST,你就拥有了一把解决"连接问题"的万能钥匙。从城市规划到数据分析,从游戏开发到网络设计,这棵"树"能帮你在复杂世界中找到最优路径。

在这里插入图片描述

一、为什么要了解最小生成树?

想象一下,你是一家电信公司的工程师,需要在10个城市之间铺设光纤网络。每两个城市之间铺设光纤的成本各不相同,有的需要穿山越岭,有的只需平原铺设。你的任务是:用最少的钱让所有城市都能互相通信。

这就是最小生成树(Minimum Spanning Tree, MST)要解决的经典问题。

简单来说,最小生成树就是在一个连通图中,找到一组边,它们能:

  • 连接所有的节点(城市)
  • 不形成环路(不绕圈子浪费)
  • 总权重(成本)最小

在图论中,有两种经典算法可以完美解决这个问题:Prim算法和Kruskal算法。它们就像两位不同风格的建筑师,用各自的方式搭建起最经济的网络。


二、最小生成树到底是什么?

用人话说

假设你有一张地图,上面标着若干城市和它们之间的道路(每条路都有修建成本)。最小生成树就是选出一些道路,确保:

  1. 所有城市都连通(从任意城市能到达任意其他城市)
  2. 没有多余的路(恰好n-1条路连接n个城市)
  3. 总修建成本最低

一个简单例子

城市A、B、C、D之间的道路成本:

在这里插入图片描述

最小生成树的选择:

在这里插入图片描述

这样4个城市就都连通了,且成本最低!


三、Prim算法:像滚雪球一样长大

卡码网:53. 寻宝
力扣:1584. 连接所有点的最小费用
1135. 最低成本连通所有城市

在这里插入图片描述

算法直觉

想象你是城市规划师,手里有一块已经开发好的区域。每次你都选择一条最便宜的道路,把一个新城市纳入你的版图。 就像滚雪球一样,你的"连通区域"越滚越大,直到覆盖所有城市。

在理解 Prim 算法之前,先记住一句话:

Prim 算法的核心目标是——让生成树一点点“长大”,每次选一条连接树内与树外节点的最便宜的边。

要想把这件事做好,就必须理解 Prim 的三个重要步骤。

Prim三部曲

在 Prim 算法中,我们维护一个关键数组:minDist
它的作用是——记录每一个节点距离当前生成树的最近距离

可以这么理解:

对于每个还没加入生成树的节点,我们都要知道:
“它离生成树有多近?最短那条边的代价是多少?”
而这些最短距离,全部存在 minDist 数组里。

Prim 算法每次迭代就是围绕 minDist 进行更新的,因此我们称它的核心流程为 Prim 三部曲

1️⃣ 选距离生成树最近的节点
 遍历 minDist 数组,找到距离最小的那个节点(还没在生成树里)。

2️⃣ 最近节点加入生成树
 把刚刚找到的节点标记为已加入(即成为生成树的一部分)。

3️⃣ 更新非生成树节点到生成树的距离
 也就是更新 minDist 数组——
 看看有没有更短的边可以让其他节点更便宜地连接进来。

这三步循环执行,直到所有节点都被纳入生成树中。
很多人第一次看 Prim 代码时一头雾水,其实只要抓住这三步,整个算法就清晰多了。

模拟过程

在算法开始时,我们会先把 minDist 数组中所有节点的距离初始化为一个很大的数。

为什么要这样做呢?

因为在最初阶段,还没有任何节点被加入最小生成树,也就是说——每个节点到生成树的“最近距离”是未知的。为了后续更新方便,我们先假设这些距离都“无限远”。这样,当我们在遍历边时,只要发现有比当前记录更小的距离,就可以立即更新对应节点在 minDist 数组中的值。

在这里插入图片描述

下面开始构造最小生成树。

第一轮:

(1) 找距离当前生成树最近的城市
因为刚开始还没有最小生成树,所以随便选择一个节点加入最小生成树。在第一部中,选择节点1(符合遍历数组的习惯,第一个遍历的是A,看作1)作为距离最小生成树最近的节点。

(2) 把它加入生成树
此时节点1已经是最小生成树中的节点。

(3) 更新其他城市到生成树的最短距离(更新 minDist 数组)
在这里插入图片描述

第二轮:

(1) 找距离当前生成树最近的城市
选择一个距离最小生成树(节点1)最近的非最小生成树中的节点,节点3距离最小生成树(节点1)最近,加入最小生成树。

(2) 把它加入生成树
此时节点1和节点3已经是最小生成树中的节点。

(3) 更新其他城市到生成树的最短距离(更新 minDist 数组)

在这里插入图片描述

第三轮:

(1) 找距离当前生成树最近的城市
选择一个距离最小生成树(节点1、节点3)最近的非最小生成树中的节点,节点4距离最小生成树(节点1、节点3)最近,加入最小生成树。

(2) 把它加入生成树
此时节点1、节点3、节点4已经是最小生成树中的节点。

(3) 更新其他城市到生成树的最短距离(更新 minDist 数组)

这里节点4到节点2的距离为5,比原先的距离值2大,所以不更新。

在这里插入图片描述

第四轮:

(1) 找距离当前生成树最近的城市
此时唯未加入节点为 2,选入。

(2) 把它加入生成树
现在所有节点被包含。

(3) 更新其他城市到生成树的最短距离(更新 minDist 数组)
无剩余节点,算法结束。

在这里插入图片描述

你会发现,每一次选边的过程,其实都是在执行 “Prim 三部曲”:
选最近节点 → 加入生成树 → 更新距离。

实现代码(Java、C++)

Java版
class Solution{public int minDistResult(int v,int[][] grid) {// 所有节点到最小生成树的最短距离(默认为最大值)// v 表示节点数int[] minDist = new int[v + 1];Arrays.fill(minDist, Integer.MAX_VALUE);// 这个节点是否在树中boolean[] isInTree = new boolean[v + 1];// 只需要循环 n - 1 次,建立 n - 1 条边for(int i = 1;i < v;i++) {int cur = 1;int minVal = Integer.MAX_VALUE;// 1.prim第一步:选择 非生成树节点 距离 最小生成树 最近的节点for(int j = 1;j <= v;j++) {if(!isInTree[j] && minDist[j] < minVal) {minVal = minDist[j];cur = j;}}// 2.prim第二步:将最近的节点加入最小生成树isInTree[cur] = true;// 3.prim第三步:更新 所有非生成树节点 到 最小生成树 的距离for(int j = 1;j <= v;j++) {if(!isInTree[j] && grid[cur][j] < minDist[j]) {minDist[j] = grid[cur][j];}}}int result = 0;// 不统计第一个顶点for(int i = 2;i <= v;i++) {result += minDist[i];}return result;}
}
C++版
class Solution {
public:int minDistResult(int v, vector<vector<int>>& grid) {// 1. 所有节点到最小生成树的最短距离(初始化为最大值)vector<int> minDist(v + 1, INT_MAX);// 2. 记录节点是否已加入最小生成树vector<bool> isInTree(v + 1, false);// Prim 三部曲循环 v - 1 次(建立 v - 1 条边)for (int i = 1; i < v; i++) {// --- Prim 第一步:选择距离最小生成树最近的非树节点 ---int cur = 1; // 默认起点为节点1int minVal = INT_MAX;for (int j = 1; j <= v; j++) {if (!isInTree[j] && minDist[j] < minVal) {minVal = minDist[j];cur = j;}}// --- Prim 第二步:将最近的节点加入最小生成树 ---isInTree[cur] = true;// --- Prim 第三步:更新所有非生成树节点到最小生成树的最短距离 ---for (int j = 1; j <= v; j++) {if (!isInTree[j] && grid[cur][j] < minDist[j]) {minDist[j] = grid[cur][j];}}}// 计算最小生成树总权值(不统计起点)int result = 0;for (int i = 2; i <= v; i++) {result += minDist[i];}return result;}
};

以上代码的整体时间复杂度为O(n²),其中n为节点的数量。


四、Kruskal算法:挑拣最划算的边

卡码网:53. 寻宝
力扣:1584. 连接所有点的最小费用
1135. 最低成本连通所有城市

在这里插入图片描述

算法直觉

现在换个角度想:你是采购员,面前有一堆道路建设报价单。你的策略是:先把所有报价从低到高排序,然后从最便宜的开始选。

prim 算法是维护节点的集合,而 Kruskal 是维护边的集合。

Kruskal思路

kruskal的思路:

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

模拟过程

首先要将图中的边按照权值从小到大排序,这样从贪心的角度来说,优先选择将权值小的边加入最小生成树。

排序后的边的顺序为[(3,4),(2,3),(1,3),(1,2),(2,4),(1,4)]。

注:先后顺序不影响结果,无向图

第一轮:

选择边(3,4),节点3和节点4不在同一个集合中,所以可以将边(3,4)加入最小生成树,并将节点3、节点4放到同一集合中。

在这里插入图片描述

第二轮:

选择边(2,3),节点2和节点3不在同一个集合中,所以可以将边(2,3)加入最小生成树,并将节点2、节点3放到同一集合中。

在这里插入图片描述

第三轮:

选择边(1,3),节点1和节点3不在同一个集合中,所以可以将边(1,3)加入最小生成树,并将节点1、节点3放到同一集合中。

此时已经生成了一个最小生成树。

在这里插入图片描述

第四轮:

选择边(1,2),节点1和节点2在同一个集合中,不做计算。后续(2,4),(1,4)同理。

在这里插入图片描述

关键技术:并查集

怎么快速判断"两个城市是否已经连通"?
用一种叫 并查集(Union-Find) 的数据结构,它能:

  • 快速判断两个节点是否在同一连通分量(connected)
  • 快速合并两个连通分量(union)

实现代码(Java、C++)

依据力扣1584. 连接所有点的最小费用撰写

Java版
class Solution {public int minCostConnectPoints(int[][] points) {int n = points.length;List<Edge> edges = new ArrayList<>();// 构造完整图的所有边for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {int dist = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]);edges.add(new Edge(i, j, dist));}}// 按边权从小到大排序edges.sort(Comparator.comparingInt(e -> e.val));UF uf = new UF(n);int result = 0;// Kruskal 主循环for (Edge edge : edges) {if (!uf.connected(edge.u, edge.v)) {uf.union(edge.u, edge.v);result += edge.val;}}return result;}
}class Edge {int u, v, val;Edge(int u, int v, int val) {this.u = u;this.v = v;this.val = val;}
}class UF {private int count;private int[] parent;public UF(int n) {this.count = n;parent = new int[n];for (int i = 0; i < n; i++) {parent[i] = i;}}public void union(int p, int q) {int rootP = find(p);int rootQ = find(q);if (rootP == rootQ) {return;}parent[rootP] = rootQ;count--;}public boolean connected(int p, int q) {int rootP = find(p);int rootQ = find(q);return rootP == rootQ;}public int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}
}
C++版
class Solution {
public:struct Edge {int u, v, val;Edge(int u, int v, int val) : u(u), v(v), val(val) {}};class UF {public:vector<int> parent;UF(int n) : parent(n) {for (int i = 0; i < n; i++) parent[i] = i;}int find(int x) {if (parent[x] != x) parent[x] = find(parent[x]);return parent[x];}bool connected(int x, int y) {return find(x) == find(y);}void unite(int x, int y) {int px = find(x), py = find(y);if (px != py) parent[px] = py;}};int minCostConnectPoints(vector<vector<int>>& points) {int n = points.size();vector<Edge> edges;// 构造完全图的所有边for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {int dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);edges.emplace_back(i, j, dist);}}// 按权值从小到大排序sort(edges.begin(), edges.end(), [](const Edge &a, const Edge &b) {return a.val < b.val;});UF uf(n);int result = 0;// Kruskal 主循环for (auto &edge : edges) {if (!uf.connected(edge.u, edge.v)) {uf.unite(edge.u, edge.v);result += edge.val;}}return result;}
};

以上代码的整体时间复杂度为O(nlogn),时间复杂度:nlogn (快排) + logn (并查集) ,n为边的数量。


五、Prim vs Kruskal:该选谁?

到这里,我们已经介绍了 KruskalPrim 两种最小生成树的算法。那么,什么时候该选择哪一种算法呢?

两者的 关键区别 在于:

  • Prim 维护的是节点集合,每次从生成树中选择与未加入节点距离最近的节点;
  • Kruskal 维护的是边集合,每次选择权值最小的边,并判断加入后是否形成环。

因此在不同图的结构下,性能表现有所差异:

  1. 稀疏图(节点较多,边相对较少):

    • Kruskal 更适合,因为它只对边进行排序和遍历。
    • 边少意味着排序和遍历的开销较小,算法效率更高。
  2. 稠密图(节点数量固定,边接近完全图):

    • Prim 更适合,因为它操作的是节点,而与边的数量关系不大。
    • 即使边很多,Prim 只需维护每个节点到生成树的最短距离。

简单总结一句话:

稀疏图用 Kruskal,稠密图用 Prim。


下次当你在规划旅行路线、设计网络拓扑、或者只是在地图上连点成线时,不妨想想:这背后是不是也藏着一棵最小生成树呢?

http://www.dtcms.com/a/606120.html

相关文章:

  • php视频网站开发实战企业怎么做app网址
  • redis不能获取连接,Could not get a resource from the pool
  • 做百度推广网站咱们做春节网页制作素材
  • Oracle 中的物理备份
  • 做服装网站需要什么条件3d模拟装修设计软件
  • 如何在手机上开自己的网站建行app怎么注册登录
  • 跨平台Hybrid App开发实战指南
  • 网站开发struts瑞昌网络推广
  • winfrom 自定义空间 ,UcCustomDataGridView
  • Spring Boot环境配置
  • 新版Xsens Link可穿戴式动捕设备
  • 淘客网站如何做推广莱芜网站建设案例
  • Linux 上怎么跑 Python 脚本
  • 微服务污点分析
  • 科学小制作 小发明 简单 手工网站seo策划方案
  • 手搓UEFI.h
  • MySQL(六) - 视图管理
  • R语言在线编译器 | 提供快速便捷的编程环境,助力数据分析与学习
  • 网站没有备案是假的吗什么是大型门户网站
  • 做电脑网站与手机上的一样吗网站建设维护需要懂哪些知识
  • UE5 PAK 封包 加载实用方法
  • UE5蓝图实现物体自动沿样条线运动
  • 基于Fovea算法的AI机械手目标检测模型详解
  • 十大景观设计网站上海有名的设计工作室
  • TR3D: Towards Real-Time Indoor 3D Object Detection论文精读
  • Vue 3 函数式编程与Composition API
  • 数据结构——四十一、分块查找(索引顺序查找)(王道408)
  • 苏州网站建设公司有哪几家还可以的洛阳制作网站的公司哪家好
  • 源码篇 虚拟DOM
  • Pig4Cloud微服务分布式ID生成:Snowflake算法深度集成指南