Kruskal算法和Prim算法求最小生成树

给定一个带权值的无向图,要求权值之和最小的生成树,常用的算法有Kruskal算法和Prim算法。这两个算法其实都是贪心思想的使用,但又能求出最优解。(代码借鉴http://blog.csdn.net/u014488381)

 

一.Kruskal算法

Kruskal算法的基本思想:先将所有边按权值从小到大排序,然后按顺序选取每条边,假如一条边的两个端点不在同一个集合中,就将这两个端点合并到同一个集合中;假如两个端点在同一个集合中,说明这两个端点已经连通了,就将当前这条边舍弃掉;当所有顶点都在同一个集合时,说明最小生成树已经形成。(写代码的时候会将所有边遍历一遍)

 

来看一个例子:

步骤:

(1)先根据权值把边排序:

AD 5

CE 5

DF 6

AB 7

BE 7

BC 8

EF 8

BD 9

EG 9

FG 11

 

(2)

选择AD这条边,将A、D加到同一个集合1中

选择CE这条边,将C、E加到同一个集合2中(不同于AD的集合)

选择DF这条边,由于D已经在集合1中,因此将F加入到集合1中,集合变为A、D、F

选择AB这条边,同理,集合1变为A、B、D、F

选择BE这条边,由于B在集合1中,E在集合2中,因此将两个集合合并,形成一个新的集合ABCDEF

由于E、F已经在同一集合中,舍弃掉BC这条边;同理舍弃掉EF、BD

选择EG这条边,此时所有元素都已经在同一集合中,最小生成树形成

象征性地舍弃掉FG这条边

 

 

实现代码如下:

  1. #include <iostream>
  2. #include <cstring>
  3. #define MaxSize 20
  4. using namespace std;
  5. struct Edge{
  6. int begin;
  7. int end;
  8. int weight;
  9. };
  10. struct Graph{
  11. char ver[MaxSize + 1];
  12. int edg[MaxSize][MaxSize];
  13. };
  14. void CreateGraph(Graph *g) {
  15. int VertexNum;
  16. char Ver;
  17. int i = 0;
  18. cout << "输入图的顶点:" << endl;
  19. while ((Ver = getchar()) != \'\n\') {
  20. g->ver[i] = Ver;
  21. i++;
  22. }
  23. g->ver[i] = \'\0\';
  24. VertexNum = strlen(g->ver);
  25. cout << "输入相应的邻接矩阵" << endl;
  26. for (int i = 0; i < VertexNum; i++) {
  27. for (int j = 0; j < VertexNum; j++) {
  28. cin >> g->edg[i][j]; //输入0则为没有边相连啊
  29. }
  30. }
  31. }
  32. void PrintGraph(Graph g) {
  33. int VertexNum = strlen(g.ver);
  34. cout << "图的顶点为:" << endl;
  35. for (int i = 0; i < VertexNum; i++) {
  36. cout << g.ver[i] << " ";
  37. }
  38. cout << endl;
  39. cout << "图的邻接矩阵为:" << endl;
  40. for (int i = 0; i < VertexNum; i++) {
  41. for (int j = 0; j < VertexNum; j++) {
  42. cout << g.edg[i][j] << " ";
  43. }
  44. cout << endl;
  45. }
  46. }
  47. int getVerNum(Graph g) {
  48. return strlen(g.ver);
  49. }
  50. int getEdgeNum(Graph g) {
  51. int res = 0;
  52. int VertexNum = getVerNum(g);
  53. for (int i = 0; i < VertexNum; i++) {
  54. //邻接矩阵对称,计算上三角元素和即可
  55. for (int j = i + 1 /*假设没有自己指向自己的*/; j < VertexNum; j++) {
  56. if (g.edg[i][j] != 0) res++;
  57. }
  58. }
  59. return res;
  60. }
  61. Edge *CreateEdges(Graph g) {
  62. int k = 0;
  63. int EdgeNum = getEdgeNum(g);
  64. int VertexNum = getVerNum(g);
  65. Edge * p = new Edge[EdgeNum];
  66. for (int i = 0; i < VertexNum; i++) {
  67. for (int j = i; j < VertexNum; j++) {
  68. if (g.edg[i][j] != 0) {
  69. p[k].begin = i;
  70. p[k].end = j;
  71. p[k].weight = g.edg[i][j];
  72. k++;
  73. }
  74. }
  75. }
  76. for (int i = 0; i < EdgeNum - 1; i++) {
  77. Edge minWeightEdge = p[i];
  78. for (int j = i + 1; j < EdgeNum; j++) {
  79. if (minWeightEdge.weight > p[j].weight) {
  80. Edge temp = minWeightEdge;
  81. minWeightEdge = p[j];
  82. p[j] = temp;
  83. }
  84. }
  85. p[i] = minWeightEdge;
  86. }
  87. return p;
  88. }
  89. void Kruskal(Graph g) {
  90. int VertexNum = getVerNum(g);
  91. int EdgeNum = getEdgeNum(g);
  92. Edge *p = CreateEdges(g);
  93. int *index = new int[VertexNum]; //index数组,其元素为连通分量的编号,index[i]==index[j]表示编号为i和j的顶点在同一连通分量中
  94. int *MSTEdge = new int[VertexNum - 1]; //用来存储已确定的最小生成树的**边的编号**,共VertexNum-1条边
  95. int k = 0;
  96. int WeightSum = 0;
  97. int IndexBegin, IndexEnd;
  98. for (int i = 0; i < VertexNum; i++) {
  99. index[i] = -1; //初始化所有index为-1
  100. }
  101. for (int i = 0; i < VertexNum - 1; i++) {
  102. for (int j = 0; j < EdgeNum; j++) {
  103. if ( !(index[p[j].begin] >= 0 && index[p[j].end] >= 0 && index[p[j].begin] == index[p[j].end] /*若成立表明p[j].begin和p[j].end已在同一连通块中(且可相互到达,废话)*/) ) {
  104. MSTEdge[i] = j;
  105. if (index[p[j].begin] == -1 && index[p[j].end] == -1) {
  106. index[p[j].begin] = index[p[j].end] = i;
  107. }
  108. else if (index[p[j].begin] == -1 && index[p[j].end] >= 0) {
  109. index[p[j].begin] = i;
  110. IndexEnd = index[p[j].end];
  111. for (int n = 0; n < VertexNum; n++) {
  112. if (index[n] == IndexEnd) {
  113. index[n] == i;
  114. }
  115. }
  116. }
  117. else if (index[p[j].begin] >= 0 && index[p[j].end] == -1) {
  118. index[p[j].end] = i;
  119. IndexBegin = index[p[j].begin];
  120. /*将连通分量合并(或者说将没加入连通分量的顶点加进去,然后将原来连通分量的值改了)*/
  121. for (int n = 0; n < VertexNum; n++) {
  122. if (index[n] == IndexBegin) {
  123. index[n] == i;
  124. }
  125. }
  126. }
  127. else {
  128. IndexBegin = index[p[j].begin];
  129. IndexEnd = index[p[j].end];
  130. for (int n = 0; n < VertexNum; n++) {
  131. if (index[n] == IndexBegin || index[n] == IndexEnd) {
  132. index[n] = i;
  133. }
  134. }
  135. }
  136. break;
  137. }
  138. }
  139. }
  140. cout << "MST的边为:" << endl;
  141. for (int i = 0; i < VertexNum - 1; i++) {
  142. cout << g.ver[p[MSTEdge[i]].begin] << "--" << g.ver[p[MSTEdge[i]].end] << endl;
  143. WeightSum += p[MSTEdge[i]].weight;
  144. }
  145. cout << "MST的权值为:" << WeightSum << endl;
  146. }

 

二.Prim算法(代码还没理解)

Prim算法的基本思想:设置两个存放顶点的集合,第一个集合初始化为空,第二个集合初始化为一个包含所有顶点的集合。首先把图中的任意一个顶点a放进第一个集合,然后在第二个集合中找到一个顶点b,使b到第一个集合中的任意一点的权值最小,然后把b从第二个集合移到第一个集合。接着在第二个集合中找到顶点c,使c到a或b的权值比到第二个集合中的其他任何顶点到a或b的权值都要小,然后把c从第二个集合移到第一个集合中。以此类推,当第二个集合中的顶点全部移到第一个集合时,最小生成树产生。

 

以上面的图再次作为例子:

设第一个集合为V,第二个集合为U。

V={A}, U={B, C, D, E, F, G}

(1)A连接了两个顶点,B和D,AB权值为7,AD权值为5,选择权值小的一条边和相应的顶点D,将D加入集合V中。V={A, D}, U={B, C, E, F, G}

(2)观察包含V中的元素A和D的边,AB权值为7,BD权值为9,DE权值为15,DF权值为6,将F加入V中。V={A, D, F}, U={B, C, E, G}

(3)依次将B(AB)、E(BE)、C(CE)、G(EG)加入到集合V中。

(4)最小生成树的边包括:AD DF AB BE CE EG,problem solved

 

实现代码如下:

  1. #include <iostream>
  2. #include <vector>
  3. #include <cstring>
  4. using namespace std;
  5. #define MaxSize 20
  6. struct Graph{
  7. char ver[MaxSize + 1];
  8. int edg[MaxSize][MaxSize];
  9. };
  10. void CreateGraph(Graph *g) {
  11. int VertexNum;
  12. char Ver;
  13. int i = 0;
  14. cout << "输入图的顶点:" << endl;
  15. while ((Ver = getchar()) != \'\n\') {
  16. g->ver[i] = Ver;
  17. i++;
  18. }
  19. g->ver[i] = \'\0\';
  20. VertexNum = strlen(g->ver);
  21. cout << "输入相应的邻接矩阵" << endl;
  22. for (int i = 0; i < VertexNum; i++) {
  23. for (int j = 0; j < VertexNum; j++) {
  24. cin >> g->edg[i][j]; //输入0则为没有边相连啊
  25. }
  26. }
  27. }
  28. void PrintGraph(Graph g) {
  29. int VertexNum = strlen(g.ver);
  30. cout << "图的顶点为:" << endl;
  31. for (int i = 0; i < VertexNum; i++) {
  32. cout << g.ver[i] << " ";
  33. }
  34. cout << endl;
  35. cout << "图的邻接矩阵为:" << endl;
  36. for (int i = 0; i < VertexNum; i++) {
  37. for (int j = 0; j < VertexNum; j++) {
  38. cout << g.edg[i][j] << " ";
  39. }
  40. cout << endl;
  41. }
  42. }
  43. int getVerNum(Graph g) {
  44. return strlen(g.ver);
  45. }
  46. //将不邻接的顶点之间的权值设为
  47. void SetWeight(Graph *g) {
  48. for (int i = 0; i < getVerNum(*g); i++) {
  49. for (int j = 0; j < getVerNum(*g); j++) {
  50. if (g->edg[i][j] == 0) {
  51. g->edg[i][j] = INT_MAX;
  52. }
  53. }
  54. }
  55. }
  56. void Prim(Graph g, int *parent) {
  57. //V为所有顶点的集合,U为最小生成树的节点集合
  58. int lowcost[MaxSize]; //lowcost[k]保存着编号为k的顶点到U中所有顶点的最小权值
  59. int closest[MaxSize]; //closest[k]保存着U到V-U中编号为k的顶点权值最小的顶点的编号
  60. int used[MaxSize];
  61. int min;
  62. int VertexNum = getVerNum(g);
  63. for (int i = 0; i < VertexNum; i++) {
  64. lowcost[i] = g.edg[0][i];
  65. closest[i] = 0;
  66. used[i] = 0;
  67. parent[i] = -1;
  68. }
  69. used[0] = 1;
  70. for (int i = 0; i < VertexNum - 1; i++) {
  71. int j = 0;
  72. min = INT_MAX;
  73. for (int k = 1; k < VertexNum; k++) { //找到V-U中的与U中顶点组成的最小权值的边的顶点编号
  74. if (used[k] == 0 && lowcost[k] < min) {
  75. min = lowcost[k];
  76. j = k;
  77. }
  78. }
  79. parent[j] = closest[j];
  80. used[j] = 1;
  81. for (int k = 0; k < VertexNum; k++) { //由于j顶点加入U中,更新lowcost和closest数组中的元素,检测V-U中的顶点到j顶点的权值是否比j加入U之前的lowcost数组的元素小
  82. if (used[k] == 0 && g.edg[j][k] < lowcost[k]) {
  83. lowcost[k] = g.edg[j][k];
  84. closest[k] = j;
  85. }
  86. }
  87. }
  88. }
  89. void PrintMST(Graph g, int *parent) {
  90. int VertexNum = getVerNum(g);
  91. int weight = 0;
  92. cout << "MST的边为:" << endl;
  93. for (int i = 1; i < VertexNum; i++) {
  94. cout << g.ver[parent[i]] << "--" << g.ver[i] << endl;
  95. weight += g.edg[parent[i]][i];
  96. }
  97. cout << "MST的权值为" << weight << endl;
  98. }
  99. int main() {
  100. Graph g;
  101. int parent[20];
  102. CreateGraph(&g);
  103. PrintGraph(g);
  104. SetWeight(&g);
  105. Prim(g, parent);
  106. PrintMST(g, parent);
  107. return 0;
  108. }

 

 

三.Kruskal算法和Prim算法的适用情况

Kruskal算法适用于边稀疏的情况(要进行排序),Prim算法适用于边稠密的情况。

 

版权声明:本文为fengziwei原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/fengziwei/p/7738389.html