C++实现图 - 03 最小生成树
数据结构与算法专栏 —— C++实现
写在前面:
这一讲来讲一个图中非常重要的内容 —— 最小生成树,在此之前我们先来回顾一下生成树的概念。
生成树的定义
一个连通图的生成树是一个极小的连通子图,它包含图中全部的 n 个顶点,但只有构成一棵树的 n-1 条边。说人话就是我要用最少的边将所有结点连接起来,直接上图:
而这个原图的生成树就有一下三个:
由此可以知道对于包含 n 个顶点的无向完全图最多包含 n 的 n-2 次方颗生成树。
最小生成树
最小生成树是带权图当中所有边权之和最小的生成树,对于一个无权图它只有生成树而没有最小生成树。
我们还是直接看图理解,假设有这样一个带权无向图:
那么我们可以得到它的最小生成树:
通过计算可以知道,其所有边权之和为 2+1+1+4+5 = 23 ,是所有生成树中最小的一颗,其它生成树的边权之和都要比它的要大。
那么问题来了,我们知道最小生成树是什么了,那如何得到最小生成树呢?
最小生成树算法有很多,但是我们这里只讲最经典的两个,也是面试和笔试中常用的两个算法,克鲁斯卡尔(Kruskal)算法和普里姆(Prim)算法。
克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔算法(Kruskal)是⼀种使用贪婪方法的最小生成树算法。它的实现思路是:
1、先将所有边放在一个数组当中,并且按照边权的大小进行从小到大的排序。
2、然后从数组中选一条权值最小的边,如果不能构成回路则加入树中。
3、重复上述操作,直到所有结点都加入到树中。
我们还是直接上图:
第一步:对所有边权进行从小到大排序,开始从头遍历每一条边。
第二步:选择边权最小的边 1 -> 6 ,发现不会成环,添加此边。
第三步:选择边 5 -> 6 ,发现不会成环,添加此边。
第四步:选择边 1 -> 2 ,发现不会成环,添加此边。
第五步:选择边 4 -> 6 ,发现不会成环,添加此边。
第五步:选择边 4 -> 5 ,发现会成环,舍弃此边。
第六步:选择边 2 -> 3 ,发现不会成环,添加此边。
此时所有结点都已经在生成树中,故结束遍历。
接下来我们来看该如何实现上述操作,这里就要用到我们之前学的并查集了,记不清楚的小伙伴可以回顾我之前的文章:
树 - 08 并查集
这里因为涉及到判断是否成环的问题,所以我们要通过并查集来查找每次新加入的边的两个结点是否已经有通路。
克鲁斯卡尔算法实现的核心其实就是并查集,除此之外其实没有什么特别复杂的地方,我们直接来看代码。
全部代码
#include<bits/stdc++.h>
using namespace std;const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N]; //用于存储父结点(并查集)struct Edge {int a, b, w;//自定义排序(按照权值从小)bool operator <(const Edge& E)const{return w < E.w;}
}edges[M];//并查集操作
int find(int x)
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}int kruskal()
{sort(edges, edges + m); //对权值从小到大进行排序for (int i = 1; i <= n; i++) p[i] = i; //初始化并查集//枚举每一条边,满足要求就加入集合中int res = 0, cnt = 0;for (int i = 0; i < m; i++){int a = edges[i].a, b = edges[i].b, w = edges[i].w; //查找a和b的父结点,如果其父结点相同则说明两结点已经连通,则直接舍弃该边a = find(a), b = find(b);if (a != b){p[a] = b;res += w;cnt++;}}//如果 树中的边数 不等于 结点数量-1 ,说明不存在最小生成树if (cnt < n - 1) return INF;else return res;
}int main()
{//假设有n个结点,m条边cin >> n >> m;//输入每一条边for (int i = 0; i < m; i++){int a, b, w;scanf("%d%d%d", &a, &b, &w);edges[i] = { a,b,w };}int t = kruskal(); //判断该图是否有最小生成树if (t == INF) puts("impossible");else cout << t << endl;return 0;
}
普里姆(Prim)算法
普里姆算法在找最小生成树时,将顶点分为两类,一类是在查找的过程中已经包含在生成树中的顶点(假设为 A 类),剩下的为另一类(假设为 B 类)。
具体思路如下:
1、我们将所有边放在邻接矩阵中。
2、最开始先创建一个额外的集合,初始化放入一个结点。
3、每次从这个集合中的所有结点与它们相连的结点的边权中,找到边权最小的一条,如果不构成换,则将与之相连的结点加入这个集合当中。
4、重复上述操作,直至所有结点都在树中。
我们还是直接上图:
第一步:存入所有边。并且初始化集合,从结点 1 开始查找。
第二步:集合中相邻边权最小的是边 1 -> 6 ,将结点 6 加入集合中。
第三步:集合中相邻边权最小的是边 6 -> 5 ,将结点 5 加入集合中。
第四步:集合中相邻边权最小的是边 1 -> 2 ,将结点 2 加入集合中。
第五步:集合中相邻边权最小的是边 6 -> 4 ,将结点 4 加入集合中。
第六步:集合中相邻边权最小的是边 4 -> 5 ,但是结点 5 已经存在与集合,故舍弃该边。
第七步:集合中相邻边权最小的是边 2 -> 3 ,将结点 3 加入集合中。
至此,所有结点都已经存在与生成树中,停止遍历。
接下来我们来看看该如何实现上述操作,这里所存储边的方式是我们之前讲过的邻接矩阵,想复习的小伙伴可以跳转到之前的文章中:
图 - 01 图的概述及实现
普利姆算法相对于克鲁斯卡尔算法来说就更加的简单粗暴,直接两层循环。外面一层遍历所有结点,里面一层一方面找到外面结点到集合最短的边,另一方面更新外面结点到集合的路径,我们直接上代码。
全部代码
#include<bits/stdc++.h>
using namespace std;const int N = 510;
int g[N][N]; //存储图
int dt[N]; //存储各个节点到生成树的距离
int st[N]; //节点是否被加入到生成树中
int pre[N]; //节点的前驱节点
int n, m; //n 个节点,m 条边void prim()
{memset(dt, 0x3f, sizeof(dt));//初始化距离数组为一个很大的数(10亿左右)int res = 0;dt[1] = 0;//从 1 号节点开始生成 for (int i = 0; i < n; i++)//每次循环选出一个点加入到生成树{int t = -1;for (int j = 1; j <= n; j++)//每个节点一次判断if (!st[j] && (t == -1 || dt[j] < dt[t]))//如果没有在树中,且到树的距离最短,则选择该点t = j;st[t] = 1;// 选择该点res += dt[t];for (int i = 1; i <= n; i++)//更新生成树外的点到生成树的距离{if (dt[i] > g[t][i] && !st[i])//从 t 到节点 i 的距离小于原来距离,则更新{dt[i] = g[t][i];//更新距离pre[i] = t;//从 t 到 i 的距离更短,i 的前驱变为 t.}}}//如果总路径无限大,则说明不存在最小生成树if (res < 0x3f3f3f3f / 2)cout << res << endl;elsecout << "impossible";
}void getPath()//输出各个边
{for (int i = n; i > 1; i--)//n 个节点,所以有 n-1 条边。cout << i << " " << pre[i] << " " << endl;// i 是节点编号,pre[i] 是 i 节点的前驱节点
}int main()
{memset(g, 0x3f, sizeof(g));//各个点之间的距离初始化成很大的数cin >> n >> m;//输入节点数和边数while (m--){int a, b, w;cin >> a >> b >> w;//输出边的两个顶点和权重g[a][b] = g[b][a] = min(g[a][b], w);//存储权重}prim();//求最下生成树//getPath();//输出路径return 0;
}
如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~
【上一讲】图 - 02 图的遍历(DFS、BFS)
【下一讲】图 - 04 最短路径
C++实现图 - 03 最小生成树相关推荐
- C++编程练习(10)----“图的最小生成树“(Prim算法、Kruskal算法)
1.Prim 算法 以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树. 2.Kruskal 算法 直接寻找最小权值的边来构建最小生成树. 比较: Kruskal 算法主要是针对边来展开,边数 ...
- 算法导论之图的最小生成树
引出最小生成树,是提到电子线路设计时,要把数个元件的引脚连接在一起,使其电位相同.使n个引脚互相连通,可以使用n-1条连接线,每条连接线连接两个引脚.寻求连接线最少的方案,是最小生成树的应用.将电子线 ...
- 图的最小生成树和最短路径算法思路总结(Prim,Kruskal,Dijkstra,Floyd)
带权无向图->最小生成树算法->Prim算法: 思路: 首先,我们先设置两个集合,U_{}:一个用来放最小生成树的顶点,T_{}:一个用来放最小生成树的边.选取最开始的点V_0,将V_0放 ...
- java 最小生成树_图的最小生成树(java实现)
1.图的最小生成树(贪心算法) 我两个算法的输出都是数组表示的,当前的索引值和当前索引对应的数据就是通路,比如parent[2] = 5;即2和5之间有一个通路,第二个可能比较好理解,第一个有点混乱 ...
- 图的最小生成树(java实现)
1.图的最小生成树(贪心算法) 我两个算法的输出都是数组表示的,当前的索引值和当前索引对应的数据就是通路,比如parent[2] = 5;即2和5之间有一个通路,第二个可能比较好理解,第一个有点混乱 ...
- 数据结构值图的最小生成树
最小生成树(最小连通网) 假设在n个城市之间建立通信联络网,则连通n个城市只需要n-1条线路.这时自然会考虑这样一个问题,如何在最节省经费的前提下建立这个通信网. 在每两个城市之间都可以设置一条线路, ...
- 克鲁斯卡尔算法(Kruskal Algorithm)——图的最小生成树
听骚话讲作者 [手动滑稽] 其实早就该写这篇博客啦. 图的最小生成树 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最小生成树可以 ...
- 图的最小生成树-Kruskal算法
问题引入 [问题描述] 编写程序,利用带权无向图的邻接矩阵存储,实现图的最小生成树Kruskal算法. [输入形式] 输入图的顶点序列及图的边的情况.如样例所示.边的输入以输入-1,-1,-1作为结束 ...
- 算法:通过克鲁斯卡尔(Kruskal)算法,求出图的最小生成树
之前我给大家分享过用普利姆(Prim)算法来求出图的最小生成树(点我去看看),今天我再给大家分享一个也是求图的最小生成树的克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,就相当于先 ...
最新文章
- Android开发资料学习(转载/链接)
- Android: 生成安卓可使用的Tflite文件
- Multi-thread--提高C++性能的编程技术笔记:多线程内存池+测试代码
- Pandas Series 1
- MXY---Docker
- 手机日志抓取工具及注意事项
- 网易校招linux面试题,网易校招真题——下厨房
- ipone 固定底部兼容问题
- VsCode下通过Latex运行计算机学报的模板
- Unity自定义文件夹图标颜色 个性化Unity编译器 unity 彩色文件夹
- html里面输出大于号和小于号
- R语言使用qlnorm函数生成对数正态分布分位数数据、使用plot函数可视化对数正态分布分位数数据(logarithmic normal distribution)
- 2015年上半年数据库系统工程师考试上午真题
- Windows Subsystem for Android (WSA) 下载:在 Windows 11 上运行 Android 应用
- 智慧交通解决方案|数字孪生高速公路交通运营管理系统
- 区间DP小结(附经典例题)
- 在php中将Unicode字符转成中文
- 初学者如何搭建React开发环境并且创建react项目
- 阿里巴巴:今年营收 5000 亿,明年GMV要再增一万亿
- ica算法c语言,独立成分分析(ICA)的模拟实验(R语言)