Acwing 最小生成树

news2024/9/28 3:24:41

最小生成树

最小生成树:由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 和边数 m
    memset(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 和边的权值 w
        edges[i] = {a, b, w}; // 存储边的信息
    }
    
    int t = kruskal(); // 调用 Kruskal 算法
    if (t == INF) puts("impossible"); // 如果返回值为 INF,说明图不连通
    else cout << t << endl; // 否则输出最小生成树的权值
    
    return 0;
}

Kruskal 算法的核心思想:

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

3.对比总结

在这里插入图片描述

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

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

相关文章

大疆会搞微单相机吗,直接和索尼、佳能、尼康竞争?

网传信息&#xff0c;不知道大疆后续是否真会考虑这块的业务。 在消费类电子的cmos领域&#xff0c;类似豪威、格科、斯特威等国产公司&#xff0c;已经有了一些突破。不过在高端的单反、微单领域&#xff0c;日本还是处于绝对的垄断地位。 2023年&#xff0c;全球cmos市场占有…

操作系统复习3 malloc如何分配内存

malloc分配内存 malloc是c语言的库函数&#xff0c;不支持重载 malloc的返回值是void*类型需要强制转换 malloc申请完的内存需要用free来进行释放内存 malloc申请空间失败会返回值为空 malloc申请的是虚拟内存地址&#xff0c;只有这块内存被访问时&#xff0c;才能发生映…

基于quill2.0的富文本编辑器,Fluent Editor,支持表格,图片,表情等

官网&#xff1a;Fluent Editor | 基于 Quill 2.0 的富文本编辑器 安装 npm i opentiny/fluent-editor quill 使用案例 <template><div class"publish-form-container"><!-- TODO --><div ref"quillEditorRef" class"quill…

comfyui 工作流生成图片使用history接口获取返回时 outputs为空 问题请教,希望有大佬可以帮忙解答一下

comfyui github地址&#xff1a; GitHub - comfyanonymous/ComfyUI: The most powerful and modular diffusion model GUI, api and backend with a graph/nodes interface.The most powerful and modular diffusion model GUI, api and backend with a graph/nodes interface.…

flush cache line dirty bytes

结论&#xff1a; ARM :To mark these lines, each line of the cache has an associated dirty bit (or bits) 所以这个依赖硬件实现&#xff0c;可能只刷出dirty bytes of cache line to memory.有的硬件只有一个dirty bit&#xff0c;所以会刷出整条cache line x86没有找…

1--苍穹外卖-SpringBoot项目介绍及环境搭建 详解

目录 软件开发整体流程 软件开发流程 角色分工 软件环境 苍穹外卖项目介绍 项目介绍 产品原型 技术选型 开发环境搭建 前端环境搭建 后端环境搭建 完善登录功能 导入接口文档 Swagger 介绍 使用方式 常用注解 软件开发整体流程 软件开发流程 需求分析&#x…

Supabase 入门指南

Supabase 是一个开源替代品&#xff0c;用于 Firebase 提供的后端服务。它基于 PostgreSQL&#xff0c;提供实时数据库、身份验证、存储等功能。本文将深入探讨 Supabase 的主要功能&#xff0c;并结合不同场景给出代码实例。 1. 创建 Supabase 项目 首先&#xff0c;访问 S…

Cannon-es物理引擎中物体动力控制的深度探索

本文目录 前言1、cannon-es给物体施加力1.1 前置代码1.2 效果1.3 给小球施加力1. applyForce效果 2. applyImpulse效果 3. applyLocalImpulse效果 4. applyTorque效果 区别总结 前言 在三维物理引擎的世界里&#xff0c;Cannon-ES以其轻量级和高效能著称&#xff0c;为开发者提…

寿司检测系统源码分享

寿司检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

zabbix“专家坐诊”第257期问答

问题一 Q&#xff1a;zabbix5.0监控项里的键值&#xff0c;怎么设置变量值&#xff1f;{#ABC} {$ABC} 都识别不到变量。 A&#xff1a;可以参考一下这个。 问题二 Q&#xff1a;我想问一下用odbc创建监控项&#xff0c;生成了json格式&#xff0c;如何创建一个触发器去判断里面…

精准控制交易亏损:掌握关键止损点设置技巧

前段时间&#xff0c;咱们探讨了开仓的策略&#xff0c;那今天就需要来聊聊止损的技巧了。一个好的开始很重要&#xff0c;但知道何时止损也是同样关键。市场波动大&#xff0c;机会稍纵即逝。当趋势变得不明确时&#xff0c;止损可以帮助我们管理风险&#xff0c;防止损失加剧…

Spring Cloud Gateway 之动态uri 自定义过滤器

背景&#xff1a;第三方公司 请求本公司入参和出参一样的同一个接口&#xff0c;根据业务类型不一样需要不同业务微服务处理 &#xff0c;和第三方公司协商在请求头中加入业务类型方便我公司在网关成分发请求。 1&#xff1a;在spring cloud gateway yml 中加入路由 重点是 -…

STL——map和set【map和set的介绍和使用】【multimap和multiset】

目录 map和set1.关联式容器2.键值对3.树形结构的关联式容器3.1set3.1.1set的介绍3.1.2set的使用3.1.2.1set的模版参数列表3.1.2.2set的构造3.1.2.3set的迭代器3.1.2.4set基本接口的使用3.1.2.5set使用案例 3.2map3.2.1map介绍3.2.2map的使用3.2.2.1map的构造3.2.2.2map的迭代器…

一个必会算法模型,XGBoost !!

大家好&#xff0c;今天咱们来聊聊XGBoost ~ XGBoost&#xff08;Extreme Gradient Boosting&#xff09;是一种集成学习算法&#xff0c;是梯度提升树的一种改进。它通过结合多个弱学习器&#xff08;通常是决策树&#xff09;来构建一个强大的集成模型。 XGBoost 的核心原理…

数据结构练习题————(二叉树)——考前必备合集!

今天在牛客网和力扣上带来了数据结构中二叉树的进阶练习题 1.二叉搜索树与双向链表———二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 2.二叉树遍历————二叉树遍历_牛客题霸_牛客网 (nowcoder.com) 3.二叉树的层序遍历————102. 二叉树的层序遍历 - 力扣&am…

C# 一键清空控件值

_场景&#xff1a;_在任何一个Form表单的操作页面或者数据台账的查询页面&#xff0c;基本都会看到一个清除的按钮&#xff0c;其功能就是用来清除我们需要抛弃的已经写入到控件内的数据。如果一个个控件来处理的话&#xff0c;想必会非常麻烦&#xff0c;而且系统不单单只是一…

杭州算力小镇:AI泛化解锁新机遇,探寻AI Agent 迭代新路径

人工智能技术不断迭代&#xff0c;重点围绕着两个事情&#xff0c;一是数据&#xff0c;二是算力。 算法的迭代推动着AI朝向多模态的方向发展&#xff0c;使之能够灵活应对不同领域的不同任务&#xff0c;模型的任务执行能力大大提升&#xff0c;人工智能泛化能力被推上高潮。…

scala 2.12 error: value foreach is not a member of Object

如图所示&#xff1a;在scala 2.11的时候下面的不报错&#xff0c;但是在2.12下报错了 在scala2.12环境下错误如下&#xff1a; 经过查找资料得到&#xff1a;df 后面加上rdd 即可

kafka下载配置

下载安装 参开kafka社区 zookeeperkafka消息队列群集部署https://apache.csdn.net/66c958fb10164416336632c3.html 下载 kafka_2.12-3.2.0安装包快速下载地址分享 官网下载链接地址&#xff1a; 官网下载地址&#xff1a;https://kafka.apache.org/downloads 官网呢下载慢…

详细解读,F5服务器负载均衡的技术优势

在现代大规模、高流量的网络使用场景中&#xff0c;为应对高并发和海量数据的挑战&#xff0c;服务器负载均衡技术应运而生。但凡知道服务器负载均衡这一名词的&#xff0c;基本都对F5有所耳闻&#xff0c;因为负载均衡正是F5的代表作&#xff0c;换句通俗易懂的话来说&#xf…