图论:最小生成树

今天要介绍两中最小生成树的算法,分别是prim算法和kruskal算法。

最小生成树是所有节点的最小连通子图,即:以最小的成本边的权值)将图中所有节点链接到一起。

图中有n个节点,那么一定可以用n-1条边将所有节点连接到一起

那么如何选择这n-1条边就是最小生成树算法的任务所在。

prim算法:

prim算法是从节点的角度采用贪心的策略每次寻找距离最小生成树最近的节点并加入到最小生成树中。prim算法核心就是以下三步:

  1. 第一步,选距离生成树最近非生成树节点
  2. 第二步,最近节点加入生成树
  3. 第三步,更新非生成树节点到生成树的距离(即更新ans数组)

ans数组用来记录每一个节点距离最小生成树的最近距离 

这样我们只需要遍历n-1此就能将所有的n-1条最优边给找出来!每次循环都是以上三个步骤,每次的步骤三都能选出一条最优的边,因为步骤一是从ans数组中找的,是目前所有的可达点中代价最小的选择,然后以当前的选择为起点开始遍历剩余的新的可达点以及更新其他可达点的距离最小生成树最近的距离,贪心体现在所有的与最小生成树相连的节点中找出一个代价最小的,不断的以当前最优解去遍历它的可达的节点。 

prim模板(邻接矩阵 + 遍历)

最小生成树prim算法的模板:ICPCer可拿 !

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const int inf = 0x3f3f3f3f; // 代替MAX,标准无穷大表示void solve() {int n, m;cin >> n >> m;vector<vector<int>> e(n + 1, vector<int>(n + 1, inf)); // 邻接矩阵,初始为infvector<bool> v(n + 1, false); // 是否在MST中vector<int> ans(n + 1, inf);  // 存储各点到MST的最小距离// 建图,处理重边for (int i = 1; i <= m; i++) {int u, v, x;cin >> u >> v >> x;e[u][v] = e[v][u] = min(e[u][v], x); // 无向图,取最小边}ans[1] = 0; // 从节点1开始构建MST(可任选起点)int res = 0; // 最小生成树权值和for (int i = 1; i <= n; i++) { // 循环n次,每次加入一个节点int mi = inf, index = -1;// 1. 选取未访问的、距离MST最近的节点for (int j = 1; j <= n; j++) {if (!v[j] && ans[j] < mi) {mi = ans[j];index = j;}}if (index == -1) { // 图不连通cout << "No MST" << endl;return;}v[index] = true; // 2. 加入MSTres += mi;// 3. 更新未访问节点到MST的距离for (int j = 1; j <= n; j++) {if (!v[j] && e[index][j] < ans[j]) {ans[j] = e[index][j];}}}cout << res << endl;
}int main() {IOS;int T = 1;// cin >> T;while (T--) solve();return 0;
}

prim模板(邻接表 + 优先队列) 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define pii pair<int,int>
const int inf = 0x3f3f3f3f; // 标准无穷大void solve() {int n, m;cin >> n >> m;vector<vector<pii>> e(n + 1); // 邻接表:e[u] = {v, w}vector<bool> vis(n + 1, false); // 是否在MST中vector<int> dis(n + 1, inf);   // 存储各点到MST的最小距离priority_queue<pii, vector<pii>, greater<pii>> pq; // 小根堆:{w, u}// 建图(无向图)for (int i = 1; i <= m; i++) {int u, v, w;cin >> u >> v >> w;e[u].push_back({v, w});e[v].push_back({u, w});}// 从节点1开始(可任选起点)dis[1] = 0;pq.push({0, 1});int res = 0, cnt = 0; // res: MST权值和,cnt: 已加入节点数while (!pq.empty() && cnt < n) {auto [w, u] = pq.top(); pq.pop();if (vis[u]) continue; // 跳过已访问节点vis[u] = true;       // 加入MSTres += w;cnt++;// 更新邻接节点到MST的距离for (auto [v, w] : e[u]) {if (!vis[v] && w < dis[v]) {dis[v] = w;pq.push({w, v});}}}cout << (cnt == n ? res : -1) << endl; // 输出-1表示图不连通
}int main() {IOS;int T = 1;// cin >> T;while (T--) solve();return 0;
}

下面来看一道经典的最小生成树的模版题:(纯模板 因为用以上两个模板均可通过

寻宝

题目链接:寻宝

这就是最小生成树的prim算法的模版题了,题目要求这几条路径中能够将所有点连接到一起的最短路径之和,也就是能够联通所有节点的最小代价,这时候就可以利用prim这一算法:

首先将所有的节点之间的代价用邻接矩阵存起来,因为是稠密图(点少边多所以用邻接矩阵),然后以节点1为最小生成树的根(以哪个都可以,1复合正常的逻辑而已),开始三部曲,第一二步就是选出了1作为起点,第三步就是遍历了所有与1相连的点,然后更新距离,此时来到了第二次循环还是到了第一步,在这几个点中找出距离最小生成树最近的节点并将其加入到最小生成树中(因为必须按照题目中所给的路径来,在前面的循环中就已经将目前能走的所有的路径以及可达点都走过了)所以要在这几个可达点中选出一条代价最小的路径,以此类推即可。注意这道题会卡内存,需要在输入n和m之后动态分配数组大小!

代码如下:

#include <bits/stdc++.h>
using namespace std;
// #define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e4+10;
const int MAX = 10001;
int n,m;
void pre_handle()
{} void solve()
{cin>>n>>m;vector<vector<int>> e(n+1,vector<int>(n+1,MAX));//邻接矩阵:点少边多的情况更实用vector<bool> v(n+1,false);//是否在最小生成树中vector<int> ans(n+1,MAX);//用于存放每个点到最小生成树的最短距离(最小代价)for(int i=1;i<=m;i++){int u,v,x;cin>>u>>v>>x;e[u][v] = x;e[v][u] = x;}int res=0;for(int i=2;i<=n;i++)//循环n-1次即可{int mi = MAX;int index = 1;// 1、prim三部曲,第一步:选距离生成树最近节点for(int j=1;j<=n;j++)//找非生成树中距离最小生成树中的最小距离{//  选取最小生成树节点的条件://  (1)不在最小生成树里//  (2)距离最小生成树最近的节点if(!v[j] && ans[j] < mi){//在所有的与最小生成树相连的节点中找出一个代价最小的mi = ans[j];index = j;}}// 2、prim三部曲,第二步:最近节点(index)加入生成树v[index] = true;//加入到最小生成树中// 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新ans数组)// indx节点加入之后, 最小生成树加入了新的节点,那么所有节点到 最小生成树的距离(即ans数组)需要更新一下// 由于index节点是新加入到最小生成树,那么只需要关心与 index 相连的 非生成树节点 的距离 是否比 原来 非生成树节点到生成树节点的距离更小了呢for(int j=1;j<=n;j++){if(!v[j] && e[index][j] < ans[j]) ans[j] = e[index][j];}}for(int i=2;i<=n;i++) res += ans[i];cout<<res<<endl;
}signed main()
{IOSint T=1;pre_handle();
//	cin>>T;while(T--) solve(); return 0;
} 

注意最后从2到n累加  因为遍历了n-1此只需要统计n-1条边即可,1是起点,ans[1]还是初始最大值 

特性邻接矩阵版邻接表+优先队列版
时间复杂度O(V²)O(Elog⁡V)
适用场景稠密图(E≈V²)稀疏图(E≪V²)
空间复杂度O(V²)O(V+E)
是否需要堆

根据题目数据范围选择即可,建议两者均保存为模板备用。

 kruskal算法

kruskal算法和prim算法解决的是同一个问题,两者区别在于prim算法是以点构建最小生成树的,而kruskal算法则是通过边和边构成最小生成树的,详细区别如下:

特性Kruskal算法Prim算法
核心思想贪心+并查集,按边权排序从小到大选边贪心+优先队列/邻接矩阵,从点扩展 MST
时间复杂度O(Elog⁡E)(排序占主导)邻接表+堆:O(Elog⁡V)
邻接矩阵:O(V²)
适用存储结构边集数组(适合稀疏图)邻接表(稀疏图)或邻接矩阵(稠密图)
是否需要处理连通性需用并查集判断是否成环自动处理连通性(通过点的集合扩展)
优势场景稀疏图(E≪V²)稠密图(E≈V²)

个人感觉kruskal算法还是比prim好理解一点的,只需要将最小生成树抽象为一个集合然后用并查集对两个节点是否在最小生成树中进行查询即可!是不是很容易理解呢?对并查集不熟悉的小伙伴可以看主播的上一篇博客哟~

寻宝的kruskal解法

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e4 + 10;
int n,m;
vector<int> tree(N);//并查集数组
void init()//初始化并查集数组
{for(int i=1;i<=n;i++) tree[i] = i;
}
int find(int x)//并查集查找函数
{if(x == tree[x]) return x;return tree[x] = find(tree[x]);
}
void join(int x,int y)
{x = find(x);y = find(y);if(x == y) return ;tree[x] = y; 
}
bool is(int x,int y)
{x = find(x);y = find(y);return x==y ? true : false;
}
struct edge
{int u,v,x;
};
vector<edge> e;
bool cmp(const edge& x, const edge& y)//提升排序效率!
{return x.x < y.x;
}
void solve()
{cin>>n>>m;init();for(int i=1;i<=m;i++){int u,v,x;cin>>u>>v>>x;e.push_back({u,v,x});}sort(e.begin(),e.end(),cmp);//对边权进行排序int res=0;for(auto i : e){if(!is(i.u,i.v)){join(i.u,i.v);res += i.x;}else continue;//可加可不加}cout<<res<<endl;
}signed main()
{IOSint T=1;
//	cin>>T;while(T--) solve(); return 0;
} 

kruscal的思路:

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

kruskal模板 

最小生成树kruskal算法的模板:ICPCer可拿 !

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 使用long long类型
#define endl '\n'      // 换行符
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)  // 加速输入输出
#define pii pair<int,int>  // 简写pair类型
#define fi first           // 简写pair的first
#define se second         // 简写pair的second
const int inf = 0x3f3f3f3f;  // 定义无穷大
const int N = 1e5+10;     // 最大节点数,根据题目调整int n,m;                  // n:节点数 m:边数
vector<int> tree(N);      // 并查集数组// 初始化并查集
void init()
{for(int i=1;i<=n;i++) tree[i]=i;  // 每个节点初始父节点是自己
}// 查找根节点(带路径压缩)
int find(int x)
{if(x == tree[x]) return x;        // 找到根节点return tree[x] = find(tree[x]);   // 路径压缩
}// 合并两个集合
void join(int x,int y)
{x = find(x);  // 找到x的根y = find(y);  // 找到y的根if(x == y) return;  // 已经在同一集合tree[x] = y;        // 合并集合
}// 判断两个节点是否在同一集合
bool isSame(int x, int y)
{return find(x) == find(y);
}// 边结构体
struct edge
{int u, v, w;  // u:起点 v:终点 w:边权
};vector<edge> e;  // 存储所有边// 边权比较函数(用于排序)
bool cmp(const edge& x, const edge& y)
{return x.w < y.w;  // 按边权从小到大排序
}void solve()
{cin>>n>>m;       // 输入节点数和边数init();          // 初始化并查集e.clear();       // 清空边集(多组数据时很重要)// 输入所有边for(int i=1;i<=m;i++){int u,v,w;cin>>u>>v>>w;e.push_back({u,v,w});  // 存储边}sort(e.begin(),e.end(),cmp);  // 按边权排序int res=0,cnt=0;  // res:最小生成树权值和 cnt:已选边数for(auto& i : e){if(!isSame(i.u,i.v))  // 如果边的两个端点不在同一集合{join(i.u,i.v);    // 合并集合res += i.w;       // 累加边权cnt++;            // 边数增加if(cnt == n-1) break;  // 已选够n-1条边,提前退出}}// 输出结果(如果不能形成生成树输出-1)cout<<(cnt == n-1 ? res : -1)<<endl;
}signed main()
{IOS;  // 加速输入输出int T = 1;// cin >> T;  // 多组数据时取消注释while(T--) solve();return 0;
}

 总结

此时我们已经讲完了 Kruskal 和 prim 两个解法来求最小生成树。

什么情况用哪个算法更合适呢。

Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果 一个图中,节点多,但边相对较少,那么使用Kruskal 更优。

为什么边少的话,使用 Kruskal 更优呢?

因为 Kruskal 是对边进行排序的后 进行操作是否加入到最小生成树。

边如果少,那么遍历操作的次数就少。

在节点数量固定的情况下,图中的边越少,Kruskal 需要遍历的边也就越少。

而 prim 算法是对节点进行操作的,节点数量越少,prim算法效率就越优。

所以在 稀疏图中,用Kruskal更优。 在稠密图中,用prim算法更优。

边数量较少为稀疏图,接近或等于完全图(所有节点皆相连)为稠密图

Prim 算法 时间复杂度为 O(n^2),其中 n 为节点数量,它的运行效率和图中边树无关,适用稠密图。

Kruskal算法 时间复杂度 为 nlogn,其中n 为边的数量,适用稀疏图。

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

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

相关文章

haproxy七层代理

1、负载均衡Load Balance(LB) 概念 负载均衡&#xff1a;是一种服务或基于硬件设备等实现的高可用反向代理技术&#xff0c;负载均衡将特定的业务(web服务、网络流量等)分担给指定的一个或多个后端特定的服务器或设备&#xff0c;从而提高了 公司业务的并发处理能力、保证了业务…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博文章数据可视化分析-点赞区间实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博文章数据可视化分析-点赞区间实现 视频…

Redis实战(3)-- 高级数据结构zset

有序集合&#xff08;ZSET&#xff09;&#xff1a;可以用作相关有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据…

Mistral AI开源 Magistral-Small-2507

宣布Magistral——Mistral AI推出的首款推理模型&#xff0c;专精于垂直领域、具备透明化特性与多语言推理能力。 最优秀的人类思维并非线性——它穿梭于逻辑、洞见、不确定性与发现之间。推理型语言模型让我们得以将复杂思考和深度理解交由AI增强或代劳&#xff0c;提升了人类…

【Kotlin】如何实现静态方法?(单例类、伴生对象、@JvmStatic)

静态方法 静态方法&#xff08;类方法&#xff09;&#xff1a;不需要创建实例就可以调用&#xff08;直接通过类名调用&#xff09;的方法 Java 中的静态方法&#xff08;static&#xff09; public class Util {public static void doAction() {//...} }调用&#xff1a;Util…

SQL Schema 和Pandas Schema什么意思

在数据处理和分析领域&#xff0c;SQL Schema 和 Pandas Schema 分别指的是在不同数据处理环境中数据的结构定义&#xff0c;以下为你详细介绍&#xff1a;SQL Schema含义SQL Schema&#xff08;模式&#xff09;是数据库对象的一个逻辑容器&#xff0c;它定义了数据库中表、视…

机器学习(一)KNN,K近邻算法(K-Nearest Neighbors)

&#x1f4a1; 建议初学者掌握KNN作为理解其他复杂算法&#xff08;如SVM、决策树、神经网络&#xff09;的基石。K近邻算法&#xff08;K-Nearest Neighbors, KNN&#xff09;详解&#xff1a;原理、实践与优化K近邻算法&#xff08;K-Nearest NeighboKrs&#xff0c;简称KNN&…

Qt 多线程数据库操作优化

在多线程应用中&#xff0c;数据库操作往往是性能瓶颈与稳定性风险的高发区。当多个线程同时读写数据库时&#xff0c;若处理不当&#xff0c;轻则出现查询卡顿、事务冲突&#xff0c;重则导致数据错乱、连接泄漏甚至程序崩溃。Qt作为跨平台框架&#xff0c;提供了QSql系列类支…

Qt 状态机框架:复杂交互逻辑的处理

Qt状态机框架&#xff08;Qt State Machine Framework&#xff09;是一个强大的工具&#xff0c;用于简化复杂的交互逻辑和状态管理。它基于UML状态图概念&#xff0c;提供了声明式的方式来定义对象行为&#xff0c;特别适合处理具有多种状态和转换的场景&#xff08;如GUI交互…

【docker】DM8达梦数据库的docker-compose以及一些启动踩坑

摘要&#xff1a;本文介绍了通过docker-compose配置启动达梦数据库(DM8)的方法。配置包括容器镜像、端口映射、数据卷挂载以及关键环境变量设置&#xff08;如字符集、大小写敏感等&#xff09;。也说明了启动过程可能遇到的一些问题。通过docker-compose启动达梦数据库可以按照…

服务器中的防火墙设置需要打开吗

服务器中的防火墙属于是一种保护计算机网络不会受到未经授权的网络设备所访问的技术手段&#xff0c;防火墙还有着将内部网络和外部网络进行隔离的功能&#xff0c;在网络之间创建安全屏障&#xff0c;以此来保护网络中数据的安全。防火墙是一种网络安全系统&#xff0c;可以帮…

Java项目:基于SSM框架实现的社区团购管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】

摘 要 使用旧方法对社区团购信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在社区团购信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的社区团购系统有…

介绍一下static关键字

在Java中&#xff0c;被static修饰的成员称为静态成员&#xff0c;static关键字可以用来修饰方法或者成员变量&#xff0c;且被static修饰的方法或者成员变量属于类方法或者类属性&#xff0c;也就是说被static修饰的方法或者成员变量不是单独存储在某一个对象的空间&#xff0…

【Java学习|黑马笔记|Day23】网络编程、反射、动态代理

【DAY23】 文章目录【DAY23】一.网络编程1&#xff09;三要素1.1&#xff09;IPInetAddress类的使用1.2&#xff09;端口号1.3&#xff09;协议2.1&#xff09;UDP协议发送数据2.2&#xff09;UDP协议接收数据2.3&#xff09;UDP的三种通信方式3.1&#xff09;TCP协议的发送和接…

【Zephyr】Window下的Zephyr编译和使用

工具下载 参考文档&#xff08;Getting Started Guide — Zephyr Project Documentation&#xff09;中介绍&#xff0c;可以直接通过winget下载&#xff1a; winget download Kitware.CMake Ninja-build.Ninja oss-winget.gperf python Git.Git oss-winget.dtc wget 7zip.7z…

图论(BFS)构造邻接表(运用队列实现搜索)

码蹄集OJ-夏日漫步 #include<bits/stdc.h> using namespace std; int n; int a[200010],dis[200010],qaq[1000010]; vector<int>son[200010]; int que[200010]; int main( ) {memset(qaq,-1,sizeof(qaq));memset(dis,-1,sizeof(dis));cin>>n;for(int i1;i…

vue怎么实现导入excel表功能

<el-uploadref"upload":action"aaa":on-change"importProject"name"excelFile"multiple:auto-upload"false":show-file-list"false"><el-button type"warning">导入</el-button><…

Linux DNS解析3 -- DNS解析代理配置使用

当网关设备配置了 /etc/hosts 文件时&#xff0c;确实可以为终端设备提供自定义DNS解析功能&#xff0c;但具体效果取决于网关的DNS代理服务配置。下面详细解释其工作原理和限制&#xff1a; 一、/etc/hosts 文件的作用 /etc/hosts 是本地静态域名解析文件&#xff0c;格式为&a…

历史版本的vscode下载地址

我有点厌恶vscode越来越臃肿的体积&#xff0c;也不需要层出不穷的新功能&#xff0c;于是网上找寻历史版本。 首先是这个页面&#xff1a;https://code.visualstudio.com/updates &#xff0c;但最多只显示两年&#xff0c;更早的就要手工修改地址栏&#xff0c;我试了最早的…

如何规范化项目执行

要实现项目执行的规范化&#xff0c;应做到以下几点&#xff1a;制定详细的项目执行计划、明确项目团队角色职责、建立高效沟通与协调机制、实施全面的质量与风险管理、采用合适的项目管理工具。其中&#xff0c;尤其重要的是明确项目团队角色职责&#xff0c;通过构建清晰的责…