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

Acwing 最小生成树

最小生成树

最小生成树:由n个节点,和n-1条边构成的无向图被称为G的一棵生成树,在G的所有生成树中,边的权值之和最小的生成树,被称为G的最小生成树。(换句话说就是用最小的代价把n个点都连起来)

  • Prim 算法(普利姆)
    • 朴素版Prim(时间复杂度 O ( n 2 ) O(n^2) O(n2),适用于稠密图;
    • 堆优化版Prim(时间复杂度 O ( m l o g n ) O(m log n) O(mlogn),适用于稀疏图),基本不用;
  • Kruskal算法,适用于稀疏图,时间复杂度 O ( m l o g m ) O(m log m) O(mlogm).$

如果是稠密图,通常选用朴素版Prim算法,因为其思路比较简洁,代码比较短;如果是稀疏图,通常选用Kruskal算法,因为其思路比Prim简单清晰。堆优化版的Prim通常不怎么用。

1.Prim

朴素Prim
与朴素dijkstra思想几乎一样,只不过Prim算法的距离指得是点到最小生成树的集合的距离,而Dijkstra算法的距离是点到起点的距离。适合用于稠密图
Prim 算法过程:

  • 初始化 dist 数组,表示各点到最小生成树的距离。开始时设为无穷大(INF)。
  • 使用贪心策略,每次选取距离集合最近的点 t,并将其加入最小生成树。
  • 若该点距离 INF 且不是第一个节点,表示图不连通,直接返回 INF。
  • 更新其余未加入集合的点与最小生成树的最短距离。
  • 最终返回最小生成树的总权值,若图不连通则输出 impossible。

实现思路:

  • 初始化各点到集合的距离INF;
  • n次循环,每次找到集合外且距离集合最近的点t,需要先判断除第一个点外找到的距离最近的点t距离是不是INF;
    • 若是则不存在最小生成树了,结束;否则还可能存在,继续操作,用该点t来更新其它点到集合的距离(这里就是和Dijkstra最主要的区别),然后将该点t加入集合;
  • 关于集合到距离最近的点:实际上就是不在集合的点与集合内的点的各个距离的最小值,每次加入新的点都会尝试更新一遍(dist[j] = min(dist[j],g[t][j]).在Dijkstra中是dist[j] - min(dits[j],dist[t] + g[t][j]));
    在这里插入图片描述

板子:

const int N = 510, INF = 0x3f3f3f3f; // 定义最大节点数 N 和无穷大 INF
int g[N][N], dist[N]; // g[i][j] 表示从节点 i 到节点 j 的边权,dist[i] 表示当前生成树到节点 i 的最短距离
bool st[N]; // st[i] 表示节点 i 是否已经加入到最小生成树中int n, m; // n 表示节点数量,m 表示边的数量// 返回最小生成树的权值之和。如果图不连通,返回 INF
int prim() {memset(dist, 0x3f, sizeof dist); // 初始化所有节点的最短距离为 INF(即无穷大)int res = 0; // 最终的最小生成树权值之和// Prim 算法的核心,循环 n 次,每次找到一个未加入集合的最近节点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;}// 如果当前是第一个节点或者找到的节点距离集合为 INF,说明图不连通if (i && dist[t] == INF) return INF; // 无法构成最小生成树if (i) res += dist[t]; // 将该节点的边权值加入最终结果中// 更新其他未加入集合的节点到生成树的最短距离for (int j = 1; j <= n; j++) {dist[j] = min(dist[j], g[t][j]); // 使用该节点 t 更新其他节点的最短距离}st[t] = true; // 标记该节点已经加入生成树}return res; // 返回最小生成树的总权值
}

Acwing 858.Prim 算法求最小生成树
在这里插入图片描述
注意:本题干未设起点所以要迭代n次,并且图中可能存在负的自环,因此计算最小生成树的总距离要在更新各点到集合距离之前。且该图为无向图,含重边,则构建边需要注意。

具体实现代码(详解版):

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 510, INF = 0x3f3f3f3f; // 定义最大节点数 N 和无穷大 INF
int g[N][N], dist[N]; // g[i][j] 表示从节点 i 到节点 j 的边权,dist[i] 表示当前生成树到节点 i 的最短距离
bool st[N]; // st[i] 表示节点 i 是否已经加入到最小生成树中int n, m; // n 表示节点数量,m 表示边的数量// 返回最小生成树的权值之和。如果图不连通,返回 INF
int prim() {memset(dist, 0x3f, sizeof dist); // 初始化所有节点的最短距离为 INF(即无穷大)int res = 0; // 最终的最小生成树权值之和// Prim 算法的核心,循环 n 次,每次找到一个未加入集合的最近节点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;}// 如果当前是第一个节点或者找到的节点距离集合为 INF,说明图不连通if (i && dist[t] == INF) return INF; // 无法构成最小生成树if (i) res += dist[t]; // 将该节点的边权值加入最终结果中// 更新其他未加入集合的节点到生成树的最短距离for (int j = 1; j <= n; j++) {dist[j] = min(dist[j], g[t][j]); // 使用该节点 t 更新其他节点的最短距离}st[t] = true; // 标记该节点已经加入生成树}return res; // 返回最小生成树的总权值
}int main() {cin >> n >> m; // 输入节点数 n 和边数 mmemset(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); // 无向图,所以 g[a][b] 和 g[b][a] 相同}int t = prim(); // 调用 Prim 算法计算最小生成树的权值// 输出结果。如果返回的是 INF,说明图不连通,输出 "impossible"if (t == INF) puts("impossible");else cout << t << endl; // 否则输出最小生成树的权值return 0;
}

堆优化Prim:思路和堆优化的Dijkstra思路基本一样,且基本不用,对于稀疏图,不如用Kruskal,这里略过

2. Kruskal

适用于稀疏图,时间复杂度 O ( m l o g m ) O(m log m) O(mlogm).Kruskal 算法是求解无向连通图的 最小生成树(MST)的经典算法。它的核心思想是通过贪心策略,按权值从小到大逐步选择边,确保不会产生环,直到选出 n-1 条边为止。整个过程涉及使用 并查集 来判断是否形成环

  • 先将所有的边按照权重,从小到大排序;
  • 从小到大枚举每条边(a,b,w)若a,b不连通,则将这条边加入集合中(将a点和b点连接起来)实质上是一个并查集的应用(两点之间加边,看两点是否存在一个连通块)
  • 无需用邻接表、邻接矩阵存储图,直接使用结构体,表示边及其权值

在这里插入图片描述
板子:

const int N = 200010, INF = 0x3f3f3f3f;
int p[N]; // 并查集的父节点数组
int n, m; // n 表示节点数,m 表示边数// 边的结构体,表示一条边及其权值
struct Edge {int a, b, w; // a, b 为连接的两个节点,w 为权值// 重载小于运算符,用于按边权值升序排序bool operator<(const Edge &W) const {return w < W.w;}
} edges[N]; // 存储所有边// 并查集:寻找节点 x 所在集合的根节点
int find(int x) {if (p[x] != x) p[x] = find(p[x]); // 路径压缩,找到根节点并直接连接return p[x];
}// Kruskal 算法求最小生成树
int kruskal() {sort(edges, edges + m); // 按照边的权值升序排序for (int i = 1; i <= n; i++) p[i] = i; // 初始化并查集,每个节点的父节点是自己int res = 0, cnt = 0; // res 存储最小生成树的权值之和,cnt 记录最小生成树的边数// 遍历所有边for (int i = 0; i < m; i++) {int a = edges[i].a, b = edges[i].b, w = edges[i].w;// 找到两个节点的根节点a = find(a), b = find(b);if (a != b) { // 如果两点不在同一个集合中,说明它们不连通p[a] = b; // 将两点所在的集合合并res += w; // 累加这条边的权值cnt++; // 增加计数}}// 如果使用的边数不足 n-1,则说明图不连通,无法构成最小生成树if (cnt < n - 1) return INF;else return res; // 返回最小生成树的权值
}

具体实现代码(详解版):

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 200010, INF = 0x3f3f3f3f;
int p[N]; // 并查集的父节点数组
int n, m; // n 表示节点数,m 表示边数// 边的结构体,表示一条边及其权值
struct Edge {int a, b, w; // a, b 为连接的两个节点,w 为权值// 重载小于运算符,用于按边权值升序排序bool operator<(const Edge &W) const {return w < W.w;}
} edges[N]; // 存储所有边// 并查集:寻找节点 x 所在集合的根节点
int find(int x) {if (p[x] != x) p[x] = find(p[x]); // 路径压缩,找到根节点并直接连接return p[x];
}// Kruskal 算法求最小生成树
int kruskal() {sort(edges, edges + m); // 按照边的权值升序排序for (int i = 1; i <= n; i++) p[i] = i; // 初始化并查集,每个节点的父节点是自己int res = 0, cnt = 0; // res 存储最小生成树的权值之和,cnt 记录最小生成树的边数// 遍历所有边for (int i = 0; i < m; i++) {int a = edges[i].a, b = edges[i].b, w = edges[i].w;// 找到两个节点的根节点a = find(a), b = find(b);if (a != b) { // 如果两点不在同一个集合中,说明它们不连通p[a] = b; // 将两点所在的集合合并res += w; // 累加这条边的权值cnt++; // 增加计数}}// 如果使用的边数不足 n-1,则说明图不连通,无法构成最小生成树if (cnt < n - 1) return INF;else return res; // 返回最小生成树的权值
}int main() {cin >> n >> m; // 输入节点数和边数for (int i = 0; i < m; i++) {int a, b, w;cin >> a >> b >> w; // 输入每条边的两个端点 a, b 和边的权值 wedges[i] = {a, b, w}; // 存储边的信息}int t = kruskal(); // 调用 Kruskal 算法if (t == INF) puts("impossible"); // 如果返回值为 INF,说明图不连通else cout << t << endl; // 否则输出最小生成树的权值return 0;
}

Kruskal 算法的核心思想:

  • 贪心策略:每次选择权值最小的边,且不形成环;
  • 并查集:用于快速检查两个节点是否已经连通,以及合并不同的连通集合。

3.对比总结

在这里插入图片描述

相关文章:

Acwing 最小生成树

最小生成树 最小生成树:由n个节点&#xff0c;和n-1条边构成的无向图被称为G的一棵生成树&#xff0c;在G的所有生成树中&#xff0c;边的权值之和最小的生成树&#xff0c;被称为G的最小生成树。&#xff08;换句话说就是用最小的代价把n个点都连起来&#xff09; Prim 算法…...

VIM简要介绍

安装 大多数 Linux 发行版和 macOS 都预装了 VIM。如果没有&#xff0c;你可以通过包管理器安装&#xff1a; Ubuntu/Debian: sudo apt-get install vimFedora: sudo dnf install vimmacOS: brew install vim&#xff08;使用 Homebrew&#xff09;Windows: 可以从 VIM 官网下…...

.NET 6.0 使用log4net配置日志记录方法

1.包管理器引入相关包 2.添加Log4net文件夹和log4net.config配置文件(配置文件属性设为始终复制)。 3.替换 log4net.config的内容(3.1与3.2选择一个就好,只是创建日志文件有所区别) 3.1: <?xml version"1.0" encoding"utf-8"?> <configuration…...

Unity角色控制及Animator动画切换如走跑跳攻击

Unity角色控制及 Animator动画切换如走跑跳攻击 目录 Unity角色控制及 一、 概念 1、角色控制 1) CharacterController(角色控制器) 2) CapsuleCollider + Rigidbody(使用物理刚体控制) 2、角色动画-Animation、Animator 1) 旧版动画系统...

JSP+Servlet+Mybatis实现列表显示和批量删除等功能

前言 使用JSP回显用户列表&#xff0c;可以进行批量删除&#xff08;有删除确认步骤&#xff09;&#xff0c;和修改用户数据&#xff08;用户数据回显步骤&#xff09;使用servlet处理传递进来的请求参数&#xff0c;并调用dao处理数据并返回使用mybatis&#xff0c;书写dao层…...

Cannot read properties of undefined (reading ‘upgrade‘)

前端开发工具&#xff1a;VSCODE 报错信息&#xff1a; INFO Starting development server...10% building 2/2 modules 0 active ERROR TypeError: Cannot read properties of undefined (reading upgrade)TypeError: Cannot read properties of undefined (reading upgrade…...

javaJUC基础

JUC基础知识 多线程 管程 Monitor&#xff0c;也就是平时所说的锁。Monitor其实是一种同步机制&#xff0c;它的义务是保证&#xff08;同一时间&#xff09;只有一个线程可以访问被保护的数据和代码块&#xff0c;JVM中同步是基于进入和退出监视器&#xff08;Monitor管程对…...

std::distance 函数介绍

std::distance 是 C 标准库中的一个函数模板&#xff0c;用于计算两个迭代器之间的距离。它的主要作用是返回从第一个迭代器到第二个迭代器之间的元素数量。这个函数对于不同类型的迭代器&#xff08;如随机访问、双向、前向等&#xff09;都能有效工作。 函数原型 template …...

如何在Windows和Linux之间实现粘贴复制

第一步 sudo apt-get autorremove open-vm-tools第二步 sudo apt-get update第三步 sudo apt-get install open-vm-tools-desktop第四步 一直按Y&#xff0c;希望执行 Y第四步 重启 reboot然后可以实现粘贴复制。...

【第十七章:Sentosa_DSML社区版-机器学习之异常检测】

【第十七章&#xff1a;Sentosa_DSML社区版-机器学习之异常检测】 机器学习异常检测是检测数据集中的异常数据的算子&#xff0c;一种高效的异常检测算法。它和随机森林类似&#xff0c;但每次选择划分属性和划分点&#xff08;值&#xff09;时都是随机的&#xff0c;而不是根…...

【Vue】为什么 Vue 不使用 React 的分片更新?

第一&#xff0c;首先时间分片是为了解决 CPU 进行大量计算的问题&#xff0c;因为 React 本身架构的问题&#xff0c;在默认的情况下更新会进行很多的计算&#xff0c;就算使用 React 提供的性能优化 API&#xff0c;进行设置&#xff0c;也会因为开发者本身的问题&#xff0c…...

大学生科技竞赛系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;主办方管理&#xff0c;公告栏管理&#xff0c;竞赛分类管理&#xff0c;竞赛信息管理&#xff0c;报名信息管理&#xff0c;竞赛成绩管理 微信端账号功能包括&#xff1a;系统首…...

什么是聚集索引?

什么是聚集索引&#xff1f; 1、聚集索引的特点2、如何确定聚集索引3、性能优势 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 聚集索引是一种特殊的索引&#xff0c;它直接包含了表中的所有数据行。所以&#xff0c;通过聚集索引&#xf…...

Centos/fedora/openEuler 终端中文显示配置

注意&#xff1a;这里主要解决的是图形界面、远程登录界面的中文乱码问题 系统原生的终端&#xff08;如虚拟机系统显示的终端&#xff09;&#xff0c;由于使用的是十分原始的 TTY 终端&#xff0c;使用点阵字体进行显示&#xff0c;点阵字体不支持中文&#xff0c;因此无法显…...

使用kaggle命令下载数据集和模型

点击用户头像&#xff0c;点击Settings&#xff1a; 找到API&#xff0c;点击create new token&#xff0c;将自动下载kaggle.json&#xff1a; 在用户目录下创建.kaggle文件夹&#xff0c;并将下载的kaggle.json文件移动到该文件夹&#xff1a; cd ~ mv Downloads/kaggle.j…...

生信初学者教程(十一):数据校正

介绍 批次效应在生物学数据分析中是一个普遍存在的问题,它指的是由于实验过程中非生物学因素(如样本处理时间、实验条件、测序平台等)的差异,导致实验结果中混入与研究目标不相关的变异。在比较对照组和实验组时,这些非生物学因素可能引入额外的噪声,影响对生物学问题真实…...

JS设计模式之桥接模式:搭建跨越维度的通路

引言 在软件开发中&#xff0c;我们经常遇到需要对不同的抽象类进行不同的实现的情况&#xff0c;而传统的对象嵌套并不是一个优雅且可扩展的解决方案&#xff0c;因此这正是桥接模式的用武之地。桥接模式通过将抽象与实现分离&#xff0c;使得它们可以独立变化&#xff0c;从…...

苹果电脑系统重磅更新——macOS Sequoia 15 系统 新功能一 览

有了 macoS Sequoia&#xff0c;你的工作效率将再次提升&#xff1a;快速调整桌面布局&#xff0c;一目了然地浏览网页重点&#xff0c;还可以通过无线镜像功能操控你的iPhone。 下面就来看看几项出色新功能&#xff0c;还有能够全面发挥这些功能的 App 和游戏。 macOS Sequo…...

DoppelGanger++:面向数据库重放的快速依赖关系图生成

doi&#xff1a;DoppelGanger: Towards Fast Dependency Graph Generation for Database Replay&#xff0c;点击前往 文章目录 1 简介2 架构概述3 依赖关系图3.1 符号和问题定义3.2 无 IT(k) 图3.3 无 OT 图表3.4 无 OTIT 图表3.5 无 IT[OT] 图表3.6 输出确定性保证 4 重复向后…...

Linux(含麒麟操作系统)如何实现多显示器屏幕采集录制

技术背景 在操作系统领域&#xff0c;很多核心技术掌握在国外企业手中。如果过度依赖国外技术&#xff0c;在国际形势变化、贸易摩擦等情况下&#xff0c;可能面临技术封锁和断供风险。开发国产操作系统可以降低这种风险&#xff0c;确保国家关键信息基础设施的稳定运行。在一…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...