当前位置:   article > 正文

最小生成树——prim算法实现_利用prim算法求解该城市分布图的最小生成树

利用prim算法求解该城市分布图的最小生成树

案例引入

N个城市之间需要铺设一张交通网,使任意两个城市间都有交通路线直达,现已知各个城市之间铺设道路的经济成本,问该如何求算铺网的最低经济成本?为求算最低经济成本,可以假设N个城市就是连通网G的N个顶点,而求算最低成本问题可以转化为在N个城市间找到N-1条边,使这些边的权值之和达到最小。在N个顶点之间可以生成许多棵不同的生成树,而最合理的交通网就是N-1条边权值之和最小的生成树。

最小生成树定义

在一个连通网所有生成树当中,各边权值之和最小的那棵生成树就叫做连通网的最小代价树,简称最小生成树。

MST性质

在构造最小生成树中,难免脱离不了对MST性质的应用。

该性质的定义为:假设U为连通网G=(V,E)中V的非空子集合,若(u,v)是一条权值最小的边,其中,那么必定存在一棵G的最小生成树包含(u,v)。

反证法:假设G的任意一棵最小生成树都不包含(u,v),设T为G的一棵最小生成树,那么必定存在(u1,v1)为T的一条边,且 .此时若在T中加入(u,v),那么新树newT一定存在环(在T中U集与V-U集分别可以构成两个连通分量),由于,可知u与u1在U集中是连通的,v与v1在V-U集中也是连通的,那么去掉(u1,v1)则将消除树的环,同时(u,v)可使U与V-U这两个连通分量重新组合成为一棵生成树。由于(u,v)的权值比(u1,v1)大,故(u,v)必定存在于某一棵最小树中。证明完毕!

普里姆算法

算法思路

该算法也称为“加点法”,即每次都选取一条权值最小的边(u,v)加入到最小生成树中,其中,使v加入到U中并且更新U到V-U各个顶点的最小权值边;重复上述步骤,直到V=U为止。

我们可以将U看成一棵树,该树到V-U的各个顶点的权值最小边由U中的某个顶点决定,比如求算U到v的权值最小需要逐一比较U中的已有顶点到v的距离选取到达v的距离最小的顶点u,规定(u,v)为U到V-U中顶点v的权值最小边

普里姆算法的构造过程

假设N=(V,E)是连通网,TE是N上最小生成树的最小边集合。

  1. 初始化U={u0},TE={空},;

  1. 选取U到V-U的最小权值边(u,v),将v加入到U中,(u,v)加入TE。由于v的加入,需要更新U到V-U的最小权值边;

  1. 重复步骤(2),直到U=V,构造结束。

注意事项:假如存在两条权值相同的最小边,则任意选取一条边加入TE。

辅助数组

由于需要选取U到V-U的最小权值边,故需要借助一个辅助数组Assistarr[mvnum]记录U到V-U的各个最小权值边。其中数组下标顶点在邻接矩阵中的下标(待会用邻接矩阵作为连通网的存储结构),adject表示U到V-U最小权值边(u,v)中的顶点u,lowcost表示边(u,v)的最小权值,其中lowcost==0表示顶点已在U中,无需再次加入。

代码实现

  1. //文件名为:AMGraph.h
  2. #pragma once
  3. #include<iostream>
  4. using namespace std;
  5. //先实现邻接矩阵的搜索算法
  6. #define Mvnum 100 //最大顶点数
  7. typedef char VerType; //顶点数据类型
  8. typedef int ArcType; //边的数据类型
  9. typedef struct AMGraph {
  10. int vexnum, arcnum;
  11. VerType vexs[Mvnum]; //顶点信息表
  12. ArcType arcs[Mvnum][Mvnum]; //邻接矩阵
  13. }AMGraph;
  14. //创建无向网
  15. void CreateUDN(AMGraph& G);
  1. //文件名:AMGraph.cpp
  2. #include"AMGraph.h"
  3. //用邻接矩阵创建无向网
  4. void CreateUDN(AMGraph& G)
  5. {
  6. //表示无穷大,以方便生成最小生成树
  7. int inf = 999999999;
  8. //输入顶点与边的个数,图的第一部分信息
  9. cin >> G.vexnum >> G.arcnum;
  10. //判断合法性
  11. if (G.vexnum > Mvnum || G.arcnum > (G.vexnum - 1) * G.vexnum / 2)
  12. {
  13. cout << "所输入信息非法" << endl;
  14. return;
  15. }
  16. //紧接着输入顶点的信息,图的第二部分信息
  17. for (int i = 0;i < G.vexnum;i++)
  18. {
  19. cin >> G.vexs[i];
  20. }
  21. //将图的边初始化,权值全部置为inf,inf表示无穷大
  22. for (int i = 0;i < G.vexnum;i++)
  23. {
  24. for (int j = 0;j < G.vexnum;j++)
  25. G.arcs[i][j] = inf;
  26. }
  27. //输入权值
  28. for (int i = 0;i < G.arcnum;i++)
  29. {
  30. //输入v1,v2作为边(v1,v2)的顶点以及边之间的权值w
  31. //编号从0开始
  32. int v1, v2, w;
  33. //此处省略了查找v1,v2编号的过程
  34. cin >> v1 >> v2 >> w;
  35. //时刻关注合法性
  36. if (v1 == v2 || v1 >= G.vexnum || v2 >= G.vexnum
  37. || v1 < 0 || v2 < 0)
  38. {
  39. i--;
  40. continue;
  41. }
  42. if (G.arcs[v1][v2] != inf)
  43. {
  44. i--;
  45. continue;
  46. }
  47. //输入边的权值
  48. G.arcs[v1][v2] = G.arcs[v2][v1] = w;
  49. }
  50. //创建完毕
  51. }
  1. //文件名:prim.h
  2. #pragma once
  3. #include"AMGraph.h"
  4. //定义辅助数组
  5. typedef struct closedge {
  6. //最小边(u,v)在U中的顶点
  7. VerType adject;
  8. //U到k的最小权值
  9. ArcType lowcost;
  10. }closedge;
  11. //普里姆算法声明
  12. void MiniSpanTree_Prim(const AMGraph& G, VerType u);
  13. //查找下标函数
  14. int LocateVex(const AMGraph& G, VerType u);
  15. //找权值最小边,返回其在辅助数组中的下标
  16. //注意数组名本身就是一个常量地址,不能传引用
  17. int Findminarc(const AMGraph& G, const closedge* Assistarr);
  1. //文件名:prim.cpp
  2. #include"prim.h"
  3. //辅助数组
  4. closedge Assistarr[Mvnum];
  5. //查找下标函数
  6. int LocateVex(const AMGraph& G, VerType u)
  7. {
  8. for (int i = 0;i < G.vexnum;i++)
  9. {
  10. if (G.vexs[i] == u)
  11. return i;
  12. }
  13. //表示没有找到,一般都不会出现这种情况
  14. return -1;
  15. }
  16. //找权值最小边,返回其在辅助数组中的下标
  17. int Findminarc(const AMGraph& G, const closedge*Assistarr)
  18. {
  19. int mark = 999999999;
  20. int k = 0;
  21. for (int i = 0;i < G.vexnum;i++)
  22. {
  23. //排除已经在U中的顶点,该顶点下标为i
  24. if (Assistarr[i].lowcost != 0)
  25. {
  26. if (mark > Assistarr[i].lowcost)
  27. {
  28. mark = Assistarr[i].lowcost;
  29. k = i;
  30. }
  31. }
  32. }
  33. return k;
  34. }
  35. //实现普里姆算法,针对连通图而言
  36. void MiniSpanTree_Prim(const AMGraph& G, VerType u)
  37. {
  38. //查找到u的下标,更新Assistarr数组的数据
  39. int k = LocateVex(G, u);
  40. if (k == -1)
  41. {
  42. cout << "顶点不存在" << endl;
  43. return;
  44. }
  45. //更新Assistarr数组
  46. for (int i = 0;i < G.vexnum;i++)
  47. {
  48. if (i != k)
  49. {
  50. //最小边(u0,v0)在U上的顶点最初都为u
  51. Assistarr[i].adject = u;
  52. //最小边的权值最初都为(u,v0)
  53. Assistarr[i].lowcost = G.arcs[k][i];
  54. }
  55. }
  56. //规定,如果Assistarr[i].lowcost ==0,则表示下标为i的顶点
  57. //已经在U中
  58. //顶点u一开始就在数组中,故Assistarr[k].lowest = 0;
  59. Assistarr[k].lowcost = 0;
  60. //紧接着,将剩余的n-1个顶点加入到U中
  61. for (int i = 1;i < G.vexnum;i++)
  62. {
  63. //找到权值最小边(u,v),u在U中,v在V-U中,因此只需要放回最小边在
  64. //数组中的下标k
  65. int k = Findminarc(G,Assistarr);
  66. //在U中的顶点
  67. VerType u0 = Assistarr[k].adject;
  68. //通过下标k在vexs数组中找到其顶点值
  69. VerType v0 = G.vexs[k];
  70.         //打印最小生成树的一条边
  71. cout << u0 << "--" << v0 << endl;
  72. //将下标为k的顶点加入到U中
  73. Assistarr[k].lowcost = 0;
  74. //更新最小权值边
  75. for (int j = 0;j < G.vexnum;j++)
  76. {
  77. //G.arcs[k][j]表示(k,j)的权值,Assistarr[j].lowcost
  78. //表示目前U中的顶点到V-U下标为j的最小权值
  79. if (G.arcs[k][j] < Assistarr[j].lowcost)
  80. {
  81. Assistarr[j].lowcost = G.arcs[k][j];
  82. Assistarr[j].adject = G.vexs[k];
  83. }
  84. }
  85. }
  86. }

测试

  1. void test01()
  2. {
  3. AMGraph G;
  4. CreateUDN(G);
  5. MiniSpanTree_Prim(G, G.vexs[0]);
  6. }
  7. int main()
  8. {
  9. test01();
  10. return 0;
  11. }

测试结果

算法分析

在上述算法的二层循环中,第一层循环旨在将n-1条边加入到TE,故需要循环n-1次。同时,每次找最小权值边与更新最小权值边都需要扫描n次,因此时间复杂度为O(n^2)。算法的时间复杂度与顶点的个数有关,与图的存储结构无关,因此prim算法适合于稠密图。另外,由于辅助数组也会产生额外的内存空间,故空间复杂度为O(n)。

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

闽ICP备14008679号