当前位置:   article > 正文

最小生成树——prim算法(内含爆肝两晚的保姆级代码)_最小生成树prim算法

最小生成树prim算法

一.概述(看不了一点

在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边,而 w(u, v) 代表此的边权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树(Minimum Spanning Tree(MST) )。最小生成树其实是最小权重生成树的简称。

简单来说,就是给你很多个点,然后求将这些点连接起来的所有边的最小长度和。

二.图解(保你看懂

待我们先看一道题理解一下在什么情况下用prim算法求最小生成树

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。

接下来M 行每行包含三个整数Xi​,Yi​,Zi​,表示有一条长度为 Zi​ 的无向边连接结点 Xi​,Yi​。

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

输入输出样例

输入 #1

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出 #1

7

我们现在有一个无向图(就是一张图),这时候我们要从点1开始然后将所有点连接起来,计算边的长度。现在我们构建两个集合S(红色的点),V(蓝色的点),S集合中存放的是已近加入最小生成树的点,V集合中存放的是还没有加入最小生成树的点。显然刚开始时所有的点都在V集合中。

1.我们开始时初始化其他点到点1的距离即到集合S的距离都为正无穷

2.我们将ans作为所求值,刚开始应该记为零。每一次我们选择离集合里面点有最近距离的点加入集合中同时更新dist数组,以及ans的值

3.在目前情况下,S集合中只有点1,这时距离S集合最近的点应该为点2,我们将点2加入集合S中,同时将dist数组和ans的值进行更新。

4.加入点2后,现在集合中有的边为23,即ans更新为23,同时对于没有在集合中的点到集合的距离进行更新,如点7应该由36更新为1。

5.然后重复上面所有步骤,直到我们的集合中包含了所有的点......

6.直到最后,我们可以发现这些点如果能被连接起来,那么需要的最小的边的长度应该为57,即权值和为57。

三.代码来了(我们还是借用这道题来展示代码)

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。

接下来M 行每行包含三个整数Xi​,Yi​,Zi​,表示有一条长度为 Zi​ 的无向边连接结点 Xi​,Yi​。

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

输入输出样例

输入 #1

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出 #1

7

这里我们说两种代码,第一种可以理解一下(方法没错,这道题ac不了的原因是他给的范围很大是第一种方法的二维数组爆了),第二种就是是为了防止有些恶心题目开的范围太大使用的优化算法(这个可以ac哦)(第一种改了一个晚上才弄出来,第二种又爆肝了一晚上)。不说了,步入正题。

1.正常(符合上面图解)的prim代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 10001;
  4. const int INF = 998244353;
  5. int g[N][N], dist[N], n, m, res;
  6. bool vis[N];
  7. void prim()
  8. {
  9. dist[1] = 0;//把点1加入集合S,点1在集合S中,将它到集合的距离初始化为0
  10. vis[1] = true;//表示点1已经加入到了S集合中
  11. for (int i = 2; i <= n; i++)
  12. {
  13. dist[i] = min(dist[i], g[1][i]);
  14. }//用点1来更新dist数组
  15. for (int i = 2; i <= n; i++) //计数器,表示选了几次了,正在选第几个点
  16. {
  17. int temp = INF;//初始化距离为无穷大
  18. int t = -1;//接下来去寻找离集合S最近的点加入到集合中,用t记录这个点的下标
  19. for (int j = 2; j <= n; j++) //判断正在几号点
  20. {
  21. //如果这个点没有加入集合S,而且这个点到集合的距离小于temp就将下标赋给t
  22. if (vis[j] == false && dist[j] < temp)
  23. {
  24. temp = dist[j];//更新集合V到集合S的最小值
  25. t = j;//把点赋给t
  26. }
  27. }
  28. if (t == -1)//表明没有适合的点,将res赋值正无穷表示构建失败,结束函数
  29. {
  30. res = INF;
  31. return;
  32. }
  33. vis[t] = true;//表明这个点找到过了,不用再使用这个点了
  34. res += dist[t]; //注意是dist[t]而不是dist[i],这里的i只起到计次数作用
  35. for (int j = 2; j <= n; j++)
  36. {
  37. dist[j] = min(dist[j], g[t][j]);//取较小的dist[i]
  38. }
  39. }
  40. }
  41. int main()
  42. {
  43. cin >> n >> m;
  44. for (int i = 1; i <= n; i++)
  45. {
  46. for (int j = 1; j <= n; j++)
  47. {
  48. g[i][j] = INF; //设刚开始所有点之间的距离为无穷大
  49. }
  50. dist[i] = INF; //点到集合上任意一个点都为无穷大
  51. }
  52. for (int i = 1; i <= m; i++)
  53. {
  54. int u, v, w;
  55. cin >> u >> v >> w;
  56. g[u][v] = g[v][u] = min(w,g[u][v]); // u与v之间的距离为w
  57. }
  58. prim();
  59. if (res == INF)
  60. {
  61. cout << "orz" << endl;//如果还是无穷大则输出
  62. }
  63. else
  64. {
  65. cout << res << endl;//不是无穷大则输出正常值
  66. }
  67. system("pause");
  68. return 0;
  69. }

2.优化prim算法(队列)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=20001;
  4. int dist[N];//到集合的距离
  5. bool vis[N];//标记是否存入集合中
  6. int n,m,ans;
  7. typedef struct node{
  8. int pos;
  9. int dis;
  10. bool operator<(const node &x) const
  11. {
  12. return x.dis<dis;
  13. //从小到大排序
  14. }
  15. }node;
  16. typedef struct edge{
  17. int v;
  18. int w;
  19. }edge;
  20. vector<edge> connects[N];//点之间的距离
  21. const int INF=998244353;
  22. void init(){
  23. for(int i=1;i<=n;i++){
  24. dist[i]=INF;//刚开始距离全部初始化为无穷大
  25. vis[i]=false;//表示都还没有访问
  26. }
  27. }
  28. void prim(){
  29. dist[1]=0;//1的距离为零
  30. vis[1]=true;//表示1访问过了,已存入集合中
  31. priority_queue<node> q;//构建优先队列,优先计算有多条边的两个点的较小的边的值
  32. q.push({1,0});//将点1存入
  33. while(!q.empty()){
  34. int pos=q.top().pos;
  35. int dis=q.top().dis;
  36. q.pop();
  37. vis[pos]=true;//存入就标记
  38. for(int i=0;i<connects[pos].size();i++){//将一个点周围所有点进行搜索
  39. int v=connects[pos][i].v;
  40. int w=connects[pos][i].w;
  41. if(vis[v]==1){//表示已经存入了,不用再进行下面操作了
  42. continue;
  43. }
  44. if(dist[v]>w){//如果距离小于dist数组距离就更新
  45. dist[v]=w;
  46. q.push({v,dist[v]});
  47. }
  48. }
  49. }
  50. for(int i=1;i<=n;i++){
  51. ans+=dist[i];//最后将所有最小值加起来
  52. }
  53. if(ans>=INF){//如果依然是无穷大,表示为构建成功最小生成树,输出orz
  54. cout<<"orz"<<endl;
  55. }else{
  56. cout<<ans<<endl;//不是无穷大则正常输出
  57. }
  58. }
  59. int main(){
  60. cin>>n>>m;
  61. init();
  62. for(int i=1;i<=m;i++){
  63. int u,v,w;
  64. cin>>u>>v>>w;//开始存入点之间的距离
  65. connects[u].push_back({v,w});
  66. connects[v].push_back({u,w});
  67. }
  68. prim();
  69. system("pause");
  70. return 0;
  71. }

最后,完结撒花!

不要忘了轻轻关注一下这位为了这个算法爆肝了两个晚上的某帅气(趁现在还没怎么掉头发)博主!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/820706
推荐阅读
相关标签
  

闽ICP备14008679号