当前位置:   article > 正文

最小生成树(详解普里姆算法)_普里姆算法最小生成树

普里姆算法最小生成树

首先,我们来看一下图的一些基本知识点

:图 G=(V,E) 由顶点集 V 和边集 E 组成。每条边对应一个点对 (v,w),其中 v,w 属于 V 。如果图中的点对是有序的,那么该图就是有向图,反之为无向图。

邻接点:若顶点 v 与 w 之间存在一条边,则认为顶点 v 与 w 邻接。

:图中的每条边都可以对应一个数值,这种与边相关的数值称为权。

路径:在图 G 中,顶点 v1 到 vk 的路径是一个顶点序列 v1,v2,···,vk。

连通图:在无向图 G 中,若两个顶点之间存在路径,则认为这两个顶点是连通的。如果在无向图 G 中,任意两个顶点都是连通的,则称 G 是连通图。

完全图:若图中任意两个顶点之间都存在一条边,则该图为完全图。

稀疏图和稠密图:当图中边的数量比较少时,称该图为稀疏图;而当图接近完全图时,称该图为稠密图。

生成树:一个连通图的生成树是一个极小连通子图,它含有图的全部顶点,但只有足以构成一棵树的 n-1 条边。

最小生成树:对于边上带有权值的图,在图的所有生成树中,树的权值最小的生成树称为最小生成树。

接下来我们进入正题,来看看如何用普里姆算法实现最小生成树。

普里姆算法的思想:

普里姆算法的初始状态仅有一个顶点在最小生成树的顶点集合 U 中,其他顶点都在另一个由不在最小生成树上的顶点构成的集合 V 中。在后续的每一步中,通过选择所有连接最小生成树上的顶点 u 和不在树上的顶点 v 之间的边中权值最小的边 (u,v) ,将对应的顶点 v 拉入最小生成树的顶点集合中。当图中所有的顶点都已加入到树中,算法运算结束后,此时得到的 n 个顶点和 n-1 条边就构成了一棵最小生成树

 

实现方法:

以上方无向有权图为例。我们创建三个数组 Adjvex[],Lowcost[],Flag[]。Adjvex[]表示权值最小的边 (u,v)中位于最小生成树中的顶点; Lowcost[]保存不在最小生成树中的各点到最小生成树中的点的边的最小权值(初始化为无限大); Flag[]标注点是否已加入最小生成树中(初始化为0)。用图表示过程如下:

1、初始状态,我们从 1 号点出发,将1加入树中,Flag[1] 置为1。然后我们遍历点 1 的所有邻接点,把相应权值填入Lowcost中,这里点 1 到点 1 的权值我们视为0。如图:

编号12345
Adjvex11111
Lowcost0563\infty

Flag

10000

 

2、我们从 Lowcost 中选出权值最小的边对应的点(该点不在树中,即Flag对应为0),这里为点4,将其加入树中,Flag[4]置为1。

编号12345
Adjvex11111
Lowcost0563\infty
Flag10010

 

 3、接着遍历点 4 的所有邻接点,与原来的权值作比较,把较小的权值填入Lowcost 中,若新的权值取代了旧的权值,还要同时改变 Adjvex 的值为 4。

编号12345
Adjvex11414
Lowcost05237
Flag1001

 

4、重复2、3步,直到所有的点均已加入最小生成树中。此时 Lowcost 行的即为最小生成树的值,最终表格如下,最小生成树的值=1+2+3+4=10。

编号12345
Adjvex15413
Lowcost01234
Flag1111

 

 程序代码:

  1. # include <iostream>
  2. # define SIZE 20
  3. # define NEWSIZE 20
  4. # define maxn 100000000 //表示无限大
  5. using namespace std;
  6. typedef struct ArcNode { //边的结点结构类型
  7. int adjvex; //该边的终点编号
  8. int weight; //该边的权值
  9. struct ArcNode* nextarc; //指向下一条边的指针
  10. }ArcNode;
  11. typedef struct VexNode { //顶点结构
  12. char data;
  13. ArcNode* firstarc; //指向第一条与该顶点有关的边的指针
  14. }VexNode;
  15. typedef struct Graph { //邻接表结构类型
  16. VexNode* VNode; //定义邻接表
  17. int vexnum, arcnum; //顶点数和边的个数
  18. int size; //邻接表的大小
  19. }Graph;
  20. int* Adjvex; //保存在最小生成树中的顶点
  21. int* Lowcost; //保存不在最小生成树中的各点到最小生成树中的点的边的最小权值
  22. int* Flag; //标注该点是否已加入最小生成树
  23. void Create_Graph(Graph& G) //创建图(邻接表)
  24. {
  25. cout << "顶点的个数:";
  26. cin >> G.vexnum;
  27. G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
  28. G.size = SIZE;
  29. while (G.size <= G.vexnum) { //根据点的个数动态分配空间
  30. G.VNode = (VexNode*)realloc(G.VNode, (G.size + NEWSIZE) * sizeof(VexNode));
  31. G.size += NEWSIZE;
  32. }
  33. Adjvex = (int*)malloc((G.size + 10) * sizeof(int));
  34. Lowcost = (int*)malloc((G.size + 10) * sizeof(int));
  35. Flag = (int*)malloc((G.size + 10) * sizeof(int));
  36. cout << "请输入各顶点的名称:";
  37. for (int i = 1; i <= G.vexnum; i++) {
  38. cin >> G.VNode[i].data;
  39. G.VNode[i].firstarc = NULL; //邻接表初始化,所有单向链表均为空表
  40. }
  41. cout << "请输入边的个数:";
  42. cin >> G.arcnum;
  43. cout << "请输入各边起点和终点的编号及权重:" << endl;
  44. int x, y, w; //x:起始点,y:终点,w:权重
  45. ArcNode* p, * q;
  46. for (int i = 1; i <= G.arcnum; i++) {
  47. cin >> x >> y >> w;
  48. p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
  49. p->nextarc = NULL;
  50. p->adjvex = y;
  51. p->weight = w;
  52. q = G.VNode[x].firstarc;
  53. //将边按顺序插入到链表末尾
  54. if (q == NULL) {
  55. G.VNode[x].firstarc = p;
  56. }
  57. else {
  58. while (q->nextarc != NULL) {
  59. q = q->nextarc;
  60. }
  61. q->nextarc = p;
  62. }
  63. p = (ArcNode*)malloc(sizeof(ArcNode));
  64. p->nextarc = NULL;
  65. p->adjvex = x;
  66. p->weight = w;
  67. q = G.VNode[y].firstarc;
  68. if (q == NULL) {
  69. G.VNode[y].firstarc = p;
  70. }
  71. else {
  72. while (q->nextarc != NULL) {
  73. q = q->nextarc;
  74. }
  75. q->nextarc = p;
  76. }
  77. }
  78. }
  79. void MinSpanningTree_Prim(Graph& G, int v) //从顶点v出发求图的最小生成树
  80. {
  81. for (int i = 1; i <= G.vexnum; i++) { //初始化
  82. Adjvex[i] = v;
  83. Lowcost[i] = maxn;
  84. Flag[i] = 0;
  85. }
  86. Lowcost[v] = 0; //初始点对应的权置为0
  87. Flag[v] = 1; //标记初始点(即将初始点加入树中)
  88. cout << G.VNode[v].data; //输出初始点的值
  89. int num = 1; //记录目前最小生成树中的点的数目
  90. ArcNode* p;
  91. while (num < G.vexnum) {
  92. p = G.VNode[v].firstarc; //p为新加入树的点的第一个邻接点
  93. while (p != NULL) { //由于新点加入,更新各不在最小生成树中的点到最小生成树中点的最小距离
  94. if (!Flag[p->adjvex] && Lowcost[p->adjvex] > p->weight) {
  95. Lowcost[p->adjvex] = p->weight;
  96. Adjvex[p->adjvex] = v;
  97. }
  98. p = p->nextarc;
  99. }
  100. int min = maxn;
  101. for (int i = 1; i <= G.vexnum; i++) { //寻找目前到最小生成树距离最小的点(该点不在最小生成树中)
  102. if (!Flag[i] && Lowcost[i] < min) {
  103. min = Lowcost[i];
  104. v = i; //更新v为目前到最小生成树距离最小的点(该点不在最小生成树中)
  105. }
  106. }
  107. Flag[v] = 1; //标记该点(即将该点加入最小生成树中)
  108. cout << G.VNode[v].data; //输出新加入树的点的值
  109. num++; //最小生成树中的点的数目+1
  110. }
  111. }
  112. int main()
  113. {
  114. Graph G;
  115. Create_Graph(G);
  116. int sum = 0;
  117. cout << "最小生成树为:";
  118. MinSpanningTree_Prim(G, 1); //从1号点出发
  119. cout << endl;
  120. for (int i = 1; i <= G.vexnum; i++) {
  121. cout << Adjvex[i] << " ";
  122. }
  123. cout << endl;
  124. for (int i = 1; i <= G.vexnum; i++) {
  125. cout << Lowcost[i] << " ";
  126. sum += Lowcost[i]; //求最小生成树的值
  127. }
  128. cout << endl;
  129. for (int i = 1; i <= G.vexnum; i++) {
  130. cout << Flag[i] << " ";
  131. }
  132. cout << endl;
  133. cout << "最小生成树的值为:" << sum << endl;
  134. return 0;
  135. }

 运行结果:

  1. 顶点的个数:5
  2. 请输入各顶点的名称:A B C D E
  3. 请输入边的个数:7
  4. 请输入各边起点和终点的编号及权重:
  5. 1 2 5
  6. 1 3 6
  7. 1 4 3
  8. 2 5 1
  9. 3 4 2
  10. 3 5 4
  11. 4 5 7
  12. 最小生成树为:ADCEB
  13. 1 5 4 1 3
  14. 0 1 2 3 4
  15. 1 1 1 1 1
  16. 最小生成树的值为:10

总结:普里姆算法的核心部分是一个双重循环。第一层循环是对所有的顶点;第二层有两个循环,一个是遍历邻接点更新数组 ,另一个是寻找 Lowcost 中的最小值。因此,普里姆算法的时间复杂度是O(n^2)。

以上是我的学习成果,很高兴与大家分享。

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

闽ICP备14008679号