当前位置: 首页 > article >正文

基于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
     }
 }

http://www.kler.cn/news/148837.html

相关文章:

  • DGL在异构图上的GraphConv模块
  • 【Redisson】基于自定义注解的Redisson分布式锁实现
  • 堆的应用(堆排序、Top-K问题)
  • 大模型的开源闭源
  • linux -系统通用命令查询
  • viple模拟器使用(四):unity模拟器中实现沿右墙迷宫算法
  • 门面模式-C++实现
  • java中IO知识点概念
  • GoLong的学习之路,进阶,RabbitMQ (消息队列)
  • Jmeter-分布式压测(远程启动服务器,windows)
  • 代码随想录-刷题第九天
  • 通义千问 Qwen-7B-Chat-Int4 模型本地化部署
  • 机器人规划算法——movebase导航框架源码分析
  • Linux的软件安装
  • linaro交叉编译工具链下载与使用笔记
  • Nacos 端口偏移量说明
  • java文件上传以及使用阿里云OSS
  • 【ArcGIS Pro微课1000例】0038:基于ArcGIS Pro的人口密度分析与制图
  • 11 动态规划解最后一块石头的重量II
  • TiDB 在咪咕云原生场景下的实践
  • 人工智能原理复习--知识表示(一)
  • 图论 2023.11.27
  • springboot 自定义starter逐级抽取
  • MATLAB算法实战应用案例精讲-【图像处理】FPGA
  • 编写安全 JavaScript 代码的最佳实践
  • Gossip协议理解
  • Android控件全解手册 - 任意View缩放平移工具-实现思路和讲解
  • 京东大数据(京东运营数据采集):2023年10月京东牛奶乳品行业品牌销售排行榜
  • 解决:SyntaxError: Non-UTF-8 code starting with À in file but no encoding declared
  • pgsql分别获取日期中的年、月、日,并处理前台展示有小数点的情况