若存在M={0,1,2,3,4,5}这样6个节点,我们知道Prim算法构建生成树是从”顶点”这个角度来思考的,然后采用“贪心思想”
来一步步扩大化,最后形成整体最优解,而Kruskal算法有点意思,它是站在”边“这个角度在思考的,首先我有两个集合。
比如M集合中的每个元素都可以认为是一个独根树(是不是想到了并查集?)。
对图中的每条边按照权值大小进行排序。(是不是想到了优先队列?)
好了,下面该如何操作呢?
首先:我们从edges中选出权值最小的一条边来作为生成树的一条边,然后将该边的两个顶点合并为一个新的树。
然后:我们继续从edges中选出次小的边作为生成树的第二条边,但是前提就是边的两个顶点一定是属于两个集合中,如果不是
则剔除该边继续选下一条次小边。
最后:经过反复操作,当我们发现n个顶点的图中生成树已经有n-1边的时候,此时生成树构建完毕。
从图中我们还是很清楚的看到Kruskal算法构建生成树的详细过程,同时我们也看到了”并查集“和“优先队列“这两个神器
来加速我们的生成树构建。
这里我灌的是一些测试数据,同时在矩阵构建完毕后,将顶点信息放入并查集,同时将边的信息放入优先队列,方便我们在
做生成树的时候秒杀。
并查集,优先队列都有数据了,下面我们只要出队操作就行了,如果边的顶点不在一个集合中,我们将其收集作为最小生成树的一条边,
按着这样的方式,最终生成树构建完毕,怎么样,组合拳打的爽不爽?
最后是总的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; using System.IO; using System.Threading.Tasks; namespace ConsoleApplication2 { public class Program { public static void Main() { MatrixGraph graph = new MatrixGraph(); graph.Build(); var edges = graph.Kruskal(); foreach (var edge in edges) { Console.WriteLine("({0},{1})({2})", edge.startEdge, edge.endEdge, edge.weight); } Console.Read(); } } #region 定义矩阵节点 /// <summary> /// 定义矩阵节点 /// </summary> public class MatrixGraph { Graph graph = new Graph(); PriorityQueue<Edge> queue; DisjointSet<int> set; public class Graph { /// <summary> /// 顶点信息 /// </summary> public int[] vertexs; /// <summary> /// 边的条数 /// </summary> public int[,] edges; /// <summary> /// 顶点个数 /// </summary> public int vertexsNum; /// <summary> /// 边的个数 /// </summary> public int edgesNum; } #region 矩阵的构建 /// <summary> /// 矩阵的构建 /// </summary> public void Build() { //顶点数 graph.vertexsNum = 6; //边数 graph.edgesNum = 8; graph.vertexs = new int[graph.vertexsNum]; graph.edges = new int[graph.vertexsNum, graph.vertexsNum]; //构建二维数组 for (int i = 0; i < graph.vertexsNum; i++) { //顶点 graph.vertexs[i] = i; for (int j = 0; j < graph.vertexsNum; j++) { graph.edges[i, j] = int.MaxValue; } } graph.edges[0, 1] = graph.edges[1, 0] = 80; graph.edges[0, 3] = graph.edges[3, 0] = 100; graph.edges[0, 5] = graph.edges[5, 0] = 20; graph.edges[1, 2] = graph.edges[2, 1] = 90; graph.edges[2, 5] = graph.edges[5, 2] = 70; graph.edges[4, 5] = graph.edges[5, 4] = 40; graph.edges[3, 4] = graph.edges[4, 3] = 60; graph.edges[2, 3] = graph.edges[3, 2] = 10; //优先队列,存放树中的边 queue = new PriorityQueue<Edge>(); //并查集 set = new DisjointSet<int>(graph.vertexs); //将对角线读入到优先队列 for (int i = 0; i < graph.vertexsNum; i++) { for (int j = i; j < graph.vertexsNum; j++) { //说明该边有权重 if (graph.edges[i, j] != int.MaxValue) { queue.Eequeue(new Edge() { startEdge = i, endEdge = j, weight = graph.edges[i, j] }, graph.edges[i, j]); } } } } #endregion #region 边的信息 /// <summary> /// 边的信息 /// </summary> public class Edge { //开始边 public int startEdge; //结束边 public int endEdge; //权重 public int weight; } #endregion #region Kruskal算法 /// <summary> /// Kruskal算法 /// </summary> public List<Edge> Kruskal() { //最后收集到的最小生成树的边 List<Edge> list = new List<Edge>(); //循环队列 while (queue.Count() > 0) { var edge = queue.Dequeue(); //如果该两点是同一个集合,则剔除该集合 if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge)) continue; list.Add(edge.t); //然后将startEdge 和 endEdge Union起来,表示一个集合 set.Union(edge.t.startEdge, edge.t.endEdge); //如果n个节点有n-1边的时候,此时生成树已经构建完毕,提前退出 if (list.Count == graph.vertexsNum - 1) break; } return list; } #endregion } #endregion }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { /// <summary> /// 并查集 /// </summary> public class DisjointSet<T> where T : IComparable { #region 树节点 /// <summary> /// 树节点 /// </summary> public class Node { /// <summary> /// 父节点 /// </summary> public T parent; /// <summary> /// 节点的秩 /// </summary> public int rank; } #endregion Dictionary<T, Node> dic = new Dictionary<T, Node>(); public DisjointSet(T[] c) { Init(c); } #region 做单一集合的初始化操作 /// <summary> /// 做单一集合的初始化操作 /// </summary> public void Init(T[] c) { //默认的不想交集合的父节点指向自己 for (int i = 0; i < c.Length; i++) { dic.Add(c[i], new Node() { parent = c[i], rank = 0 }); } } #endregion #region 判断两元素是否属于同一个集合 /// <summary> /// 判断两元素是否属于同一个集合 /// </summary> /// <param name="root1"></param> /// <param name="root2"></param> /// <returns></returns> public bool IsSameSet(T root1, T root2) { return Find(root1).CompareTo(Find(root2)) == 0; } #endregion #region 查找x所属的集合 /// <summary> /// 查找x所属的集合 /// </summary> /// <param name="x"></param> /// <returns></returns> public T Find(T x) { //如果相等,则说明已经到根节点了,返回根节点元素 if (dic[x].parent.CompareTo(x) == 0) return x; //路径压缩(回溯的时候赋值,最终的值就是上面返回的"x",也就是一条路径上全部被修改了) return dic[x].parent = Find(dic[x].parent); } #endregion #region 合并两个不相交集合 /// <summary> /// 合并两个不相交集合 /// </summary> /// <param name="root1"></param> /// <param name="root2"></param> /// <returns></returns> public void Union(T root1, T root2) { T x1 = Find(root1); T y1 = Find(root2); //如果根节点相同则说明是同一个集合 if (x1.CompareTo(y1) == 0) return; //说明左集合的深度 < 右集合 if (dic[x1].rank < dic[y1].rank) { //将左集合指向右集合 dic[x1].parent = y1; } else { //如果 秩 相等,则将 y1 并入到 x1 中,并将x1++ if (dic[x1].rank == dic[y1].rank) dic[x1].rank++; dic[y1].parent = x1; } } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; using System.IO; namespace ConsoleApplication2 { public class PriorityQueue<T> where T : class { /// <summary> /// 定义一个数组来存放节点 /// </summary> private List<HeapNode> nodeList = new List<HeapNode>(); #region 堆节点定义 /// <summary> /// 堆节点定义 /// </summary> public class HeapNode { /// <summary> /// 实体数据 /// </summary> public T t { get; set; } /// <summary> /// 优先级别 1-10个级别 (优先级别递增) /// </summary> public int level { get; set; } public HeapNode(T t, int level) { this.t = t; this.level = level; } public HeapNode() { } } #endregion #region 添加操作 /// <summary> /// 添加操作 /// </summary> public void Eequeue(T t, int level = 1) { //将当前节点追加到堆尾 nodeList.Add(new HeapNode(t, level)); //如果只有一个节点,则不需要进行筛操作 if (nodeList.Count == 1) return; //获取最后一个非叶子节点 int parent = nodeList.Count / 2 - 1; //堆调整 UpHeapAdjust(nodeList, parent); } #endregion #region 对堆进行上滤操作,使得满足堆性质 /// <summary> /// 对堆进行上滤操作,使得满足堆性质 /// </summary> /// <param name="nodeList"></param> /// <param name="index">非叶子节点的之后指针(这里要注意:我们 /// 的筛操作时针对非叶节点的) /// </param> public void UpHeapAdjust(List<HeapNode> nodeList, int parent) { while (parent >= 0) { //当前index节点的左孩子 var left = 2 * parent + 1; //当前index节点的右孩子 var right = left + 1; //parent子节点中最大的孩子节点,方便于parent进行比较 //默认为left节点 var min = left; //判断当前节点是否有右孩子 if (right < nodeList.Count) { //判断parent要比较的最大子节点 min = nodeList[left].level < nodeList[right].level ? left : right; } //如果parent节点大于它的某个子节点的话,此时筛操作 if (nodeList[parent].level > nodeList[min].level) { //子节点和父节点进行交换操作 var temp = nodeList[parent]; nodeList[parent] = nodeList[min]; nodeList[min] = temp; //继续进行更上一层的过滤 parent = (int)Math.Ceiling(parent / 2d) - 1; } else { break; } } } #endregion #region 优先队列的出队操作 /// <summary> /// 优先队列的出队操作 /// </summary> /// <returns></returns> public HeapNode Dequeue() { if (nodeList.Count == 0) return null; //出队列操作,弹出数据头元素 var pop = nodeList[0]; //用尾元素填充头元素 nodeList[0] = nodeList[nodeList.Count - 1]; //删除尾节点 nodeList.RemoveAt(nodeList.Count - 1); //然后从根节点下滤堆 DownHeapAdjust(nodeList, 0); return pop; } #endregion #region 对堆进行下滤操作,使得满足堆性质 /// <summary> /// 对堆进行下滤操作,使得满足堆性质 /// </summary> /// <param name="nodeList"></param> /// <param name="index">非叶子节点的之后指针(这里要注意:我们 /// 的筛操作时针对非叶节点的) /// </param> public void DownHeapAdjust(List<HeapNode> nodeList, int parent) { while (2 * parent + 1 < nodeList.Count) { //当前index节点的左孩子 var left = 2 * parent + 1; //当前index节点的右孩子 var right = left + 1; //parent子节点中最大的孩子节点,方便于parent进行比较 //默认为left节点 var min = left; //判断当前节点是否有右孩子 if (right < nodeList.Count) { //判断parent要比较的最大子节点 min = nodeList[left].level < nodeList[right].level ? left : right; } //如果parent节点小于它的某个子节点的话,此时筛操作 if (nodeList[parent].level > nodeList[min].level) { //子节点和父节点进行交换操作 var temp = nodeList[parent]; nodeList[parent] = nodeList[min]; nodeList[min] = temp; //继续进行更下一层的过滤 parent = min; } else { break; } } } #endregion #region 获取元素并下降到指定的level级别 /// <summary> /// 获取元素并下降到指定的level级别 /// </summary> /// <returns></returns> public HeapNode GetAndDownPriority(int level) { if (nodeList.Count == 0) return null; //获取头元素 var pop = nodeList[0]; //设置指定优先级(如果为 MinValue 则为 -- 操作) nodeList[0].level = level == int.MinValue ? --nodeList[0].level : level; //下滤堆 DownHeapAdjust(nodeList, 0); return nodeList[0]; } #endregion #region 获取元素并下降优先级 /// <summary> /// 获取元素并下降优先级 /// </summary> /// <returns></returns> public HeapNode GetAndDownPriority() { //下降一个优先级 return GetAndDownPriority(int.MinValue); } #endregion #region 返回当前优先队列中的元素个数 /// <summary> /// 返回当前优先队列中的元素个数 /// </summary> /// <returns></returns> public int Count() { return nodeList.Count; } #endregion } }