加载中...

第十六题 Kruskal算法


这篇我们看看第二种生成树的Kruskal算法,这个算法的魅力在于我们可以打一下算法和数据结构的组合拳,很有意思的。

一:思想

    若存在M={0,1,2,3,4,5}这样6个节点,我们知道Prim算法构建生成树是从”顶点”这个角度来思考的,然后采用“贪心思想”

来一步步扩大化,最后形成整体最优解,而Kruskal算法有点意思,它是站在”边“这个角度在思考的,首先我有两个集合。

1. 顶点集合(vertexs):

     比如M集合中的每个元素都可以认为是一个独根树(是不是想到了并查集?)。

2.边集合(edges):

    对图中的每条边按照权值大小进行排序。(是不是想到了优先队列?)

好了,下面该如何操作呢?

首先:我们从edges中选出权值最小的一条边来作为生成树的一条边,然后将该边的两个顶点合并为一个新的树。

然后:我们继续从edges中选出次小的边作为生成树的第二条边,但是前提就是边的两个顶点一定是属于两个集合中,如果不是

        则剔除该边继续选下一条次小边。

最后:经过反复操作,当我们发现n个顶点的图中生成树已经有n-1边的时候,此时生成树构建完毕。

从图中我们还是很清楚的看到Kruskal算法构建生成树的详细过程,同时我们也看到了”并查集“和“优先队列“这两个神器

来加速我们的生成树构建。

 

二:构建

1.Build方法

这里我灌的是一些测试数据,同时在矩阵构建完毕后,将顶点信息放入并查集,同时将边的信息放入优先队列,方便我们在

做生成树的时候秒杀。

2:Kruskal算法

并查集,优先队列都有数据了,下面我们只要出队操作就行了,如果边的顶点不在一个集合中,我们将其收集作为最小生成树的一条边,

按着这样的方式,最终生成树构建完毕,怎么样,组合拳打的爽不爽?

最后是总的代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using System.Threading;
  7. using System.IO;
  8. using System.Threading.Tasks;
  9.  
  10. namespace ConsoleApplication2
  11. {
  12. public class Program
  13. {
  14. public static void Main()
  15. {
  16. MatrixGraph graph = new MatrixGraph();
  17.  
  18. graph.Build();
  19.  
  20. var edges = graph.Kruskal();
  21.  
  22. foreach (var edge in edges)
  23. {
  24. Console.WriteLine("({0},{1})({2})", edge.startEdge, edge.endEdge, edge.weight);
  25. }
  26.  
  27. Console.Read();
  28. }
  29. }
  30.  
  31. #region 定义矩阵节点
  32. /// <summary>
  33. /// 定义矩阵节点
  34. /// </summary>
  35. public class MatrixGraph
  36. {
  37. Graph graph = new Graph();
  38.  
  39. PriorityQueue<Edge> queue;
  40.  
  41. DisjointSet<int> set;
  42.  
  43. public class Graph
  44. {
  45. /// <summary>
  46. /// 顶点信息
  47. /// </summary>
  48. public int[] vertexs;
  49.  
  50. /// <summary>
  51. /// 边的条数
  52. /// </summary>
  53. public int[,] edges;
  54.  
  55. /// <summary>
  56. /// 顶点个数
  57. /// </summary>
  58. public int vertexsNum;
  59.  
  60. /// <summary>
  61. /// 边的个数
  62. /// </summary>
  63. public int edgesNum;
  64. }
  65.  
  66. #region 矩阵的构建
  67. /// <summary>
  68. /// 矩阵的构建
  69. /// </summary>
  70. public void Build()
  71. {
  72. //顶点数
  73. graph.vertexsNum = 6;
  74.  
  75. //边数
  76. graph.edgesNum = 8;
  77.  
  78. graph.vertexs = new int[graph.vertexsNum];
  79.  
  80. graph.edges = new int[graph.vertexsNum, graph.vertexsNum];
  81.  
  82. //构建二维数组
  83. for (int i = 0; i < graph.vertexsNum; i++)
  84. {
  85. //顶点
  86. graph.vertexs[i] = i;
  87.  
  88. for (int j = 0; j < graph.vertexsNum; j++)
  89. {
  90. graph.edges[i, j] = int.MaxValue;
  91. }
  92. }
  93.  
  94. graph.edges[0, 1] = graph.edges[1, 0] = 80;
  95. graph.edges[0, 3] = graph.edges[3, 0] = 100;
  96. graph.edges[0, 5] = graph.edges[5, 0] = 20;
  97. graph.edges[1, 2] = graph.edges[2, 1] = 90;
  98. graph.edges[2, 5] = graph.edges[5, 2] = 70;
  99. graph.edges[4, 5] = graph.edges[5, 4] = 40;
  100. graph.edges[3, 4] = graph.edges[4, 3] = 60;
  101. graph.edges[2, 3] = graph.edges[3, 2] = 10;
  102.  
  103. //优先队列,存放树中的边
  104. queue = new PriorityQueue<Edge>();
  105.  
  106. //并查集
  107. set = new DisjointSet<int>(graph.vertexs);
  108.  
  109. //将对角线读入到优先队列
  110. for (int i = 0; i < graph.vertexsNum; i++)
  111. {
  112. for (int j = i; j < graph.vertexsNum; j++)
  113. {
  114. //说明该边有权重
  115. if (graph.edges[i, j] != int.MaxValue)
  116. {
  117. queue.Eequeue(new Edge()
  118. {
  119. startEdge = i,
  120. endEdge = j,
  121. weight = graph.edges[i, j]
  122. }, graph.edges[i, j]);
  123. }
  124. }
  125. }
  126. }
  127. #endregion
  128.  
  129. #region 边的信息
  130. /// <summary>
  131. /// 边的信息
  132. /// </summary>
  133. public class Edge
  134. {
  135. //开始边
  136. public int startEdge;
  137.  
  138. //结束边
  139. public int endEdge;
  140.  
  141. //权重
  142. public int weight;
  143. }
  144. #endregion
  145.  
  146. #region Kruskal算法
  147. /// <summary>
  148. /// Kruskal算法
  149. /// </summary>
  150. public List<Edge> Kruskal()
  151. {
  152. //最后收集到的最小生成树的边
  153. List<Edge> list = new List<Edge>();
  154.  
  155. //循环队列
  156. while (queue.Count() > 0)
  157. {
  158. var edge = queue.Dequeue();
  159.  
  160. //如果该两点是同一个集合,则剔除该集合
  161. if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge))
  162. continue;
  163.  
  164. list.Add(edge.t);
  165.  
  166. //然后将startEdge 和 endEdge Union起来,表示一个集合
  167. set.Union(edge.t.startEdge, edge.t.endEdge);
  168.  
  169. //如果n个节点有n-1边的时候,此时生成树已经构建完毕,提前退出
  170. if (list.Count == graph.vertexsNum - 1)
  171. break;
  172. }
  173.  
  174. return list;
  175. }
  176. #endregion
  177. }
  178. #endregion
  179. }

并查集:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ConsoleApplication2
  7. {
  8. /// <summary>
  9. /// 并查集
  10. /// </summary>
  11. public class DisjointSet<T> where T : IComparable
  12. {
  13. #region 树节点
  14. /// <summary>
  15. /// 树节点
  16. /// </summary>
  17. public class Node
  18. {
  19. /// <summary>
  20. /// 父节点
  21. /// </summary>
  22. public T parent;
  23.  
  24. /// <summary>
  25. /// 节点的秩
  26. /// </summary>
  27. public int rank;
  28. }
  29. #endregion
  30.  
  31. Dictionary<T, Node> dic = new Dictionary<T, Node>();
  32.  
  33. public DisjointSet(T[] c)
  34. {
  35. Init(c);
  36. }
  37.  
  38. #region 做单一集合的初始化操作
  39. /// <summary>
  40. /// 做单一集合的初始化操作
  41. /// </summary>
  42. public void Init(T[] c)
  43. {
  44. //默认的不想交集合的父节点指向自己
  45. for (int i = 0; i < c.Length; i++)
  46. {
  47. dic.Add(c[i], new Node()
  48. {
  49. parent = c[i],
  50. rank = 0
  51. });
  52. }
  53. }
  54. #endregion
  55.  
  56. #region 判断两元素是否属于同一个集合
  57. /// <summary>
  58. /// 判断两元素是否属于同一个集合
  59. /// </summary>
  60. /// <param name="root1"></param>
  61. /// <param name="root2"></param>
  62. /// <returns></returns>
  63. public bool IsSameSet(T root1, T root2)
  64. {
  65. return Find(root1).CompareTo(Find(root2)) == 0;
  66. }
  67. #endregion
  68.  
  69. #region 查找x所属的集合
  70. /// <summary>
  71. /// 查找x所属的集合
  72. /// </summary>
  73. /// <param name="x"></param>
  74. /// <returns></returns>
  75. public T Find(T x)
  76. {
  77. //如果相等,则说明已经到根节点了,返回根节点元素
  78. if (dic[x].parent.CompareTo(x) == 0)
  79. return x;
  80.  
  81. //路径压缩(回溯的时候赋值,最终的值就是上面返回的"x",也就是一条路径上全部被修改了)
  82. return dic[x].parent = Find(dic[x].parent);
  83. }
  84. #endregion
  85.  
  86. #region 合并两个不相交集合
  87. /// <summary>
  88. /// 合并两个不相交集合
  89. /// </summary>
  90. /// <param name="root1"></param>
  91. /// <param name="root2"></param>
  92. /// <returns></returns>
  93. public void Union(T root1, T root2)
  94. {
  95. T x1 = Find(root1);
  96. T y1 = Find(root2);
  97.  
  98. //如果根节点相同则说明是同一个集合
  99. if (x1.CompareTo(y1) == 0)
  100. return;
  101.  
  102. //说明左集合的深度 < 右集合
  103. if (dic[x1].rank < dic[y1].rank)
  104. {
  105. //将左集合指向右集合
  106. dic[x1].parent = y1;
  107. }
  108. else
  109. {
  110. //如果 秩 相等,则将 y1 并入到 x1 中,并将x1++
  111. if (dic[x1].rank == dic[y1].rank)
  112. dic[x1].rank++;
  113.  
  114. dic[y1].parent = x1;
  115. }
  116. }
  117. #endregion
  118. }
  119. }

优先队列:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using System.Threading;
  7. using System.IO;
  8.  
  9. namespace ConsoleApplication2
  10. {
  11. public class PriorityQueue<T> where T : class
  12. {
  13. /// <summary>
  14. /// 定义一个数组来存放节点
  15. /// </summary>
  16. private List<HeapNode> nodeList = new List<HeapNode>();
  17.  
  18. #region 堆节点定义
  19. /// <summary>
  20. /// 堆节点定义
  21. /// </summary>
  22. public class HeapNode
  23. {
  24. /// <summary>
  25. /// 实体数据
  26. /// </summary>
  27. public T t { get; set; }
  28.  
  29. /// <summary>
  30. /// 优先级别 1-10个级别 (优先级别递增)
  31. /// </summary>
  32. public int level { get; set; }
  33.  
  34. public HeapNode(T t, int level)
  35. {
  36. this.t = t;
  37. this.level = level;
  38. }
  39.  
  40. public HeapNode() { }
  41. }
  42. #endregion
  43.  
  44. #region 添加操作
  45. /// <summary>
  46. /// 添加操作
  47. /// </summary>
  48. public void Eequeue(T t, int level = 1)
  49. {
  50. //将当前节点追加到堆尾
  51. nodeList.Add(new HeapNode(t, level));
  52.  
  53. //如果只有一个节点,则不需要进行筛操作
  54. if (nodeList.Count == 1)
  55. return;
  56.  
  57. //获取最后一个非叶子节点
  58. int parent = nodeList.Count / 2 - 1;
  59.  
  60. //堆调整
  61. UpHeapAdjust(nodeList, parent);
  62. }
  63. #endregion
  64.  
  65. #region 对堆进行上滤操作,使得满足堆性质
  66. /// <summary>
  67. /// 对堆进行上滤操作,使得满足堆性质
  68. /// </summary>
  69. /// <param name="nodeList"></param>
  70. /// <param name="index">非叶子节点的之后指针(这里要注意:我们
  71. /// 的筛操作时针对非叶节点的)
  72. /// </param>
  73. public void UpHeapAdjust(List<HeapNode> nodeList, int parent)
  74. {
  75. while (parent >= 0)
  76. {
  77. //当前index节点的左孩子
  78. var left = 2 * parent + 1;
  79.  
  80. //当前index节点的右孩子
  81. var right = left + 1;
  82.  
  83. //parent子节点中最大的孩子节点,方便于parent进行比较
  84. //默认为left节点
  85. var min = left;
  86.  
  87. //判断当前节点是否有右孩子
  88. if (right < nodeList.Count)
  89. {
  90. //判断parent要比较的最大子节点
  91. min = nodeList[left].level < nodeList[right].level ? left : right;
  92. }
  93.  
  94. //如果parent节点大于它的某个子节点的话,此时筛操作
  95. if (nodeList[parent].level > nodeList[min].level)
  96. {
  97. //子节点和父节点进行交换操作
  98. var temp = nodeList[parent];
  99. nodeList[parent] = nodeList[min];
  100. nodeList[min] = temp;
  101.  
  102. //继续进行更上一层的过滤
  103. parent = (int)Math.Ceiling(parent / 2d) - 1;
  104. }
  105. else
  106. {
  107. break;
  108. }
  109. }
  110. }
  111. #endregion
  112.  
  113. #region 优先队列的出队操作
  114. /// <summary>
  115. /// 优先队列的出队操作
  116. /// </summary>
  117. /// <returns></returns>
  118. public HeapNode Dequeue()
  119. {
  120. if (nodeList.Count == 0)
  121. return null;
  122.  
  123. //出队列操作,弹出数据头元素
  124. var pop = nodeList[0];
  125.  
  126. //用尾元素填充头元素
  127. nodeList[0] = nodeList[nodeList.Count - 1];
  128.  
  129. //删除尾节点
  130. nodeList.RemoveAt(nodeList.Count - 1);
  131.  
  132. //然后从根节点下滤堆
  133. DownHeapAdjust(nodeList, 0);
  134.  
  135. return pop;
  136. }
  137. #endregion
  138.  
  139. #region 对堆进行下滤操作,使得满足堆性质
  140. /// <summary>
  141. /// 对堆进行下滤操作,使得满足堆性质
  142. /// </summary>
  143. /// <param name="nodeList"></param>
  144. /// <param name="index">非叶子节点的之后指针(这里要注意:我们
  145. /// 的筛操作时针对非叶节点的)
  146. /// </param>
  147. public void DownHeapAdjust(List<HeapNode> nodeList, int parent)
  148. {
  149. while (2 * parent + 1 < nodeList.Count)
  150. {
  151. //当前index节点的左孩子
  152. var left = 2 * parent + 1;
  153.  
  154. //当前index节点的右孩子
  155. var right = left + 1;
  156.  
  157. //parent子节点中最大的孩子节点,方便于parent进行比较
  158. //默认为left节点
  159. var min = left;
  160.  
  161. //判断当前节点是否有右孩子
  162. if (right < nodeList.Count)
  163. {
  164. //判断parent要比较的最大子节点
  165. min = nodeList[left].level < nodeList[right].level ? left : right;
  166. }
  167.  
  168. //如果parent节点小于它的某个子节点的话,此时筛操作
  169. if (nodeList[parent].level > nodeList[min].level)
  170. {
  171. //子节点和父节点进行交换操作
  172. var temp = nodeList[parent];
  173. nodeList[parent] = nodeList[min];
  174. nodeList[min] = temp;
  175.  
  176. //继续进行更下一层的过滤
  177. parent = min;
  178. }
  179. else
  180. {
  181. break;
  182. }
  183. }
  184. }
  185. #endregion
  186.  
  187. #region 获取元素并下降到指定的level级别
  188. /// <summary>
  189. /// 获取元素并下降到指定的level级别
  190. /// </summary>
  191. /// <returns></returns>
  192. public HeapNode GetAndDownPriority(int level)
  193. {
  194. if (nodeList.Count == 0)
  195. return null;
  196.  
  197. //获取头元素
  198. var pop = nodeList[0];
  199.  
  200. //设置指定优先级(如果为 MinValue 则为 -- 操作)
  201. nodeList[0].level = level == int.MinValue ? --nodeList[0].level : level;
  202.  
  203. //下滤堆
  204. DownHeapAdjust(nodeList, 0);
  205.  
  206. return nodeList[0];
  207. }
  208. #endregion
  209.  
  210. #region 获取元素并下降优先级
  211. /// <summary>
  212. /// 获取元素并下降优先级
  213. /// </summary>
  214. /// <returns></returns>
  215. public HeapNode GetAndDownPriority()
  216. {
  217. //下降一个优先级
  218. return GetAndDownPriority(int.MinValue);
  219. }
  220. #endregion
  221.  
  222. #region 返回当前优先队列中的元素个数
  223. /// <summary>
  224. /// 返回当前优先队列中的元素个数
  225. /// </summary>
  226. /// <returns></returns>
  227. public int Count()
  228. {
  229. return nodeList.Count;
  230. }
  231. #endregion
  232. }
  233. }


还没有评论.