基于C#实现Kruskal算法

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

一、思想

若存在 M={0,1,2,3,4,5}这样 6 个节点,我们知道 Prim 算法构建生成树是从”顶点”这个角度来思考的,然后采用“贪心思想”来一步步扩大化,最后形成整体最优解,而 Kruskal 算法有点意思,它是站在”边“这个角度在思考的,首先我有两个集合。

1.1、顶点集合(vertexs)

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

1.2、边集合(edges)

对图中的每条边按照权值大小进行排序。(是不是想到了优先队列?)
首先:我们从 edges 中选出权值最小的一条边来作为生成树的一条边,然后将该边的两个顶点合并为一个新的树。
然后:我们继续从 edges 中选出次小的边作为生成树的第二条边,但是前提就是边的两个顶点一定是属于两个集合中,如果不是则剔除该边继续选下一条次小边。
最后:经过反复操作,当我们发现 n 个顶点的图中生成树已经有 n-1 边的时候,此时生成树构建完毕。
image.png
image.png
从图中我们还是很清楚的看到 Kruskal 算法构建生成树的详细过程,同时我们也看到了”并查集“和“优先队列“这两个神器来加速我们的生成树构建。

二、构建

2.1、Build 方法

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

 #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

2.2、Kruskal 算法

并查集,优先队列都有数据了,下面我们只要出队操作就行了,如果边的顶点不在一个集合中,我们将其收集作为最小生成树的一条边,按着这样的方式,最终生成树构建完毕。

 #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

最后是总的代码:

 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
     }
 }
  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Kruskal算法是一种用于生成最小生成树的贪心算法。最小生成树是一种包含所有节点但是权重之和最小的树。下面是一个使用Kruskal算法实现最小生成树的Python代码示例: ```python # 定义边的类 class Edge: def __init__(self, u, v, w): self.u = u self.v = v self.w = w # 定义Kruskal算法 def kruskal(n, edges): # 初始化并查集 parent = [i for i in range(n)] rank = [0] * n # 边按权重从小到大排序 edges.sort(key=lambda x: x.w) # 初始化最小生成树的权重和 mst_weight = 0 # 遍历每条边 for e in edges: # 判断两个端点是否在同一个集合中 p1 = find(parent, e.u) p2 = find(parent, e.v) # 如果不在同一个集合中,则将它们合并,并将该边加入最小生成树 if p1 != p2: union(parent, rank, p1, p2) mst_weight += e.w return mst_weight # 定义并查集的find和union操作 def find(parent, i): if parent[i] != i: parent[i] = find(parent, parent[i]) return parent[i] def union(parent, rank, x, y): xroot = find(parent, x) yroot = find(parent, y) if rank[xroot] < rank[yroot]: parent[xroot] = yroot elif rank[xroot] > rank[yroot]: parent[yroot] = xroot else: parent[yroot] = xroot rank[xroot] += 1 # 测试代码 n = 5 edges = [Edge(0, 1, 2), Edge(0, 3, 6), Edge(1, 2, 3), Edge(1, 3, 8), Edge(1, 4, 5), Edge(2, 4, 7), Edge(3, 4, 9)] mst_weight = kruskal(n, edges) print("最小生成树的权重和为:", mst_weight) ``` 这个代码示例中,我们使用了一个边的类来表示每条边,包含它的两个端点和它的权重。然后我们定义了Kruskal算法和并查集的find和union操作。最后,我们测试了这个算法,生成了一个包含5个节点的图,并计算了它的最小生成树的权重和。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙别闹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值