当前位置:   article > 正文

【数据结构】图的最小生成树_图数据库 最小生成树

图数据库 最小生成树

最小生成树

一个图中有N个顶点,边的数量一定是>=N-1,我们从中选取N-1条边,用来连接N个点,所形成的边权之和最小,就是最小生成树。

构成最小生成树的准则

  1. 只能使用图中的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路

构成最小生成树的的方法:Kruskal(克鲁斯卡尔)算法和Prim(普里姆)算法

二者都是基于逐步贪心的算法

Kruskal算法

Kruskal算法的思路

  1. 先构建出一个N个顶点的最小生成树图,其中不包含任何边,将原图的各个边按照权值进行排序
  2. 在排序的边中,选出一条边,如果这条边不会与其它边构成环,就添加到最小生成树图里
  3. 当选边数为N-1时,就构成最小生成树,否则不构成

描述这一过程,构成最小生成树

再选边时 都会构成环,此时已经是n-1条边,连接n个顶点,是最小生成树。

  • 要做选取最小的边,就需要将边的关系放到优先级队列中。每一个取边,就是top pop的过程。
  • 判断是否成环,需要一个集合,如果顶点A和B都在集合中,那么就构成环。
  • 只要优先级队列还有边就要一直判断选边,直到队列为空,如果最后选取了n-1条边,那么就是最小生成树,反之不是,并返回所有的权和。
  1. struct Edge
  2. {
  3. size_t _srci;
  4. size_t _dsti;
  5. W _w;
  6. Edge(size_t srci, size_t dsti, const W& w)
  7. :_srci(srci)
  8. , _dsti(dsti)
  9. , _w(w)
  10. {}
  11. bool operator>(const Edge& e) const
  12. {
  13. return _w > e._w;
  14. }
  15. bool operator<(const Edge& e) const
  16. {
  17. return _w < e._w;
  18. }
  19. };
  20. //最小生成树
  21. W Kruskal(Self& minTree)
  22. {
  23. size_t n = _vertexs.size();
  24. minTree._vertexs = _vertexs;
  25. minTree._vIndexMap = _vIndexMap;
  26. minTree._matrix.resize(n);
  27. for (size_t i = 0; i < minTree._vertexs.size(); i++)
  28. {
  29. minTree._matrix[i].resize(n, MAX_W);
  30. }
  31. //优先级队列
  32. priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
  33. //把所有的边都放进去
  34. for (size_t i = 0; i < _matrix.size(); i++)
  35. {
  36. for (size_t j = 0; j < _matrix[i].size(); j++)
  37. {
  38. if (i<j&&_matrix[i][j] != MAX_W)
  39. {
  40. minque.push(Edge(i, j, _matrix[i][j]));
  41. }
  42. }
  43. }
  44. //选边
  45. W total = W();
  46. size_t i = 1;
  47. UnionFindSet ufs(_vertexs.size());
  48. while (!minque.empty())
  49. {
  50. Edge minedge = minque.top();
  51. minque.pop();
  52. if (!ufs.InSet(minedge._srci,minedge._dsti))
  53. {
  54. cout << _vertexs[minedge._srci] << "->" << _vertexs[minedge._dsti] << ":" << minedge._w << endl;
  55. ufs.Union(minedge._srci, minedge._dsti);
  56. minTree._AddEdge(minedge._srci, minedge._srci, minedge._w);
  57. total += minedge._w;
  58. ++i;
  59. }
  60. else
  61. {
  62. cout << "构成环" << endl;
  63. }
  64. }
  65. if (i == _vertexs.size())
  66. {
  67. return total;
  68. }
  69. return W();
  70. }
  1. 创建最小生成树的顶点和映射,提前为邻接矩阵开空间。
  2. 遍历原图的邻接矩阵,将边都放到优先级队列中。
  3. 选边时,只有不在同一个集合中,才被添加到最小生成树的边里。

对于自定义类型的优先级队列,需要自定比较函数。

这里也运用到并查集的知识。


Prim算法

算法的基本思路

  • 构造一个含 n 个顶点、不含任何边的图作为最小生成树,将图中的顶点分为两个集合,X Y 集合中的顶点是已经连接到最小生成树中的顶点,Y集合中的顶点是还没有连接到最小生成树中的顶点,刚开始时X 集合中只包含给定的起始顶点。
  • 每次从连接X 集合与 Y 集合的所有边中选出一条权值最小的边,将其加入到最小生成树中,由于选出来的边对应的两个顶点一个属于X 集合,另一个属于Y集合,因此是不会构成回路的。

步骤

  1. 先创建最小生成树的图,构造顶点和下标映射,为邻接矩阵开辟空间。
  2. 创建 X Y标记数组,X是已经包含的集合(全false),Y是没有被包含的集合(true)。
  3. 求出srci表示从哪一个顶点开始。X[srci]=true,Y[srci]=false
  4. 将srci所有相邻的边都放到优先级队列中,遍历优先级队列,如果不构成环,就添加边
  5. 然后将dsti的边相邻也添加到队列中
  6. 确保不相邻的方法:srci在X中,dsti在Y中,就是不相邻
  1. W Prim(Self& minTree, const W& src)
  2. {
  3. size_t srci = GetVertexIndex(src);
  4. size_t n = _vertexs.size();
  5. minTree._vertexs = _vertexs;
  6. minTree._vIndexMap = _vIndexMap;
  7. minTree._matrix.resize(n);
  8. for (size_t i = 0; i < minTree._vertexs.size(); i++)
  9. {
  10. minTree._matrix[i].resize(n, MAX_W);
  11. }
  12. vector<bool> X(n, false);
  13. vector<bool> Y(n, true);
  14. X[srci] = true;
  15. Y[srci] = false;
  16. //优先级队列
  17. priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
  18. //把所有的边都放进去
  19. for (size_t i = 0; i < _matrix.size(); i++)
  20. {
  21. if ( _matrix[srci][i] != MAX_W)
  22. {
  23. minque.push(Edge(srci, i, _matrix[srci][i]));
  24. }
  25. }
  26. cout << "Prim 选边" << endl;
  27. W total = W();
  28. size_t i = 1;
  29. while (!minque.empty())
  30. {
  31. Edge minedge = minque.top();
  32. minque.pop();
  33. if (X[minedge._dsti])
  34. {
  35. //cout << "构成环";
  36. }
  37. else
  38. {
  39. minTree._AddEdge(minedge._srci, minedge._dsti, minedge._w);
  40. X[minedge._dsti] = true;
  41. Y[minedge._dsti] = false;
  42. ++i;
  43. total += minedge._w;
  44. if (i == n) break;
  45. //将dsti相邻的都放到队列中
  46. for (size_t index = 0; index < n; index++)
  47. {
  48. if (Y[index]&&_matrix[minedge._dsti][index] != MAX_W)
  49. {
  50. minque.push(Edge(minedge._dsti, index,_matrix[minedge._dsti][index]));
  51. }
  52. }
  53. }
  54. }
  55. if (i == n)
  56. {
  57. return total;
  58. }
  59. return W();
  60. }

画图演示这个过程

........省略几步类似的步骤

选择 i-g  i-h a-h时都会成环,不操作

最终的结果

最后依旧需要判断,如果完成n-1次选边后,可以构成最小生成树

否则,无法构成


Prim算法每次选边都会遍历相邻的边,是时间复杂度较大的算法。

Kruskal是全局贪心,每次选边都是选择最小的边。

Prim算法是局部贪心,总是选择目前相连的最小边。

二者所得到的权值是一样的。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号