基于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;}#endregionDictionary<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.mzph.cn/news/169708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言内存管理

通常程序访问的是虚拟内存&#xff0c;虚拟内存映射到物理内存的一小部分。 在Linux系统中&#xff0c;虚拟内存默认为4G的大小。每个进程都有独立的4G内存地址空间。 int main() {char s[] "hello world"; //s数组位于栈区&#xff0c;复制了一份字符串到数组里ch…

【设计模式-2.1】创建型——单例模式

说明&#xff1a;设计模式根据用途分为创建型、结构性和行为型。创建型模式主要用于描述如何创建对象&#xff0c;本文介绍创建型中的单例模式。 饿汉式单例 单例模式是比较常见的一种设计模式&#xff0c;旨在确保对象的唯一性&#xff0c;什么时候去使用这个对象都是同一个…

MySQL 批量插入记录报 Error 1390 (HY000)

文章目录 1.背景2.问题3.分批插入4.一次最多能插入多少条记录&#xff1f;参考文献 1.背景 Golang 后台服务使用 GORM 实现与 MySQL 的交互&#xff0c;在实现一个通过 Excel 导入数据的接口时&#xff0c;使用 Save 方法一次性插入大量记录&#xff08;>1w&#xff09;时报…

LiveGBS流媒体平台GB/T28181功能-查看国标设备会话列表直播会话、回放会话、下载会话、对讲会话

LiveGBS流媒体平台GB/T28181功能-查看国标设备会话列表直播会话、回放会话、下载会话、对讲会话 1、会话列表2、会话类型3、搭建GB28181视频直播平台 1、会话列表 LiveGBS-> 国标设备-》点击在线状态 点击会话列表 2、会话类型 下拉会话类型可以看到 直播会话、回放会话、…

不用排队升级GPT/获取api

想要在国内获取api key&#xff0c;可以使用这种方法 小技巧&#xff1a;目前GPT还是排队订阅&#xff0c;可以直接用链接&#xff1a;https://chat.openai.com/invite/accepted 即可跳过排队环节 接下来先看一下如何购买腾讯云服务器 第一步&#xff1a;打开腾讯云 腾讯云 …

Visual Studio 2019 C# System.BadImageFormatException 解决方法

文章目录 1.DLL文件缺失或不匹配原因解决方法 2.系统环境变量Path下内容过多原因解决方法 3.位数错误原因解决方法 分析几种可能因素 1.DLL文件缺失或不匹配 原因 检查对应Debug路径下的DLL文件是否有缺失 解决方法 将对应的DLL文件放到Debug文件夹里面&#xff0c;检查冗余…

抖音权重查询源码H5源码

源码下载&#xff1a;123网盘

2022年12月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 默认小猫角色和气球角色都是显示状态,小猫程序如下图所示,气球没有程序,点击绿旗,舞台上最终显示的效果是?( ) A:可能出现6个不同位置的小猫和6个小球 B:可能出现6个不同位…

python 爬百度热搜并生成词云

1、爬取百度body存入txt def get_baidu_hot():url "https://top.baidu.com/board?tabrealtime"headers {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3&…

Python 前后端分离项目Vue部署应用

一、视图创建 from django.http import JsonResponse from django.shortcuts import render# Create your views here. from django.views import Viewclass IndexView(View):def get(self,request):# 前后端分离 &#xff08;前端JS代码渲染数据&#xff09;return JsonRespo…

Jensen不等式

如果是正数&#xff0c;并且它们的和等于1&#xff0c;f是凸函数&#xff0c;那么&#xff1a; 也可表述为&#xff1a; 即x期望的凸函数值小于等于x凸函数值的期望

可验证随机函数(VRF)

文章目录 一、背景以及场景共识发展第一代 POW “以力取胜”第二代 POS/DPOS “民主投票”第三代 VRF “运气抽签” 二、可验证随机函数&#xff08;VRF&#xff09;快速开始1. VRF是什么?2. MD5 hash函数和VRF&#xff08;Verifiable Random Function&#xff09;区别3. VRF-…

【刷题宝典NO.4】

目录 公交站间的距离 生命游戏 公交站间的距离 https://leetcode.cn/problems/distance-between-bus-stops/ 环形公交路线上有 n 个站&#xff0c;按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离&#xff0c;distance[i] 表示编号为 i 的车站和编号为 …

程序员职场可能遇到的问题总结!

在职场中&#xff0c;你是否遇到过这样的领导或同事&#xff0c;他可能是自恋狂&#xff0c;自吹自擂自我标榜&#xff1b;可能是团队合作的绊脚石&#xff0c;对团队合作态度消极并频繁拖后腿&#xff1b;可能是抱怨专家&#xff0c;满满负能量&#xff1b;可能是完美主义者&a…

二十一、数组(6)

本章概要 数组排序Arrays.sort的使用并行排序binarySearch二分查找parallelPrefix并行前缀 数组排序 根据对象的实际类型执行比较排序。一种方法是为不同的类型编写对应的排序方法&#xff0c;但是这样的代码不能复用。 编程设计的一个主要目标是“将易变的元素与稳定的元素…

win11渗透武器库,囊括所有渗透工具

开箱即用&#xff0c;最全的武器库&#xff0c;且都是2023年11月最新版&#xff0c;后续自己还可以再添加&#xff0c;下载地址&#xff1a;https://download.csdn.net/download/weixin_59679023/88565739 服务连接 信息收集工具 端口扫描 代理抓包 漏洞扫描 指纹识别 webshel…

移动机器人路径规划(七)--- 基于MDP的路径规划MDP-Based Planning

目录 1 什么是MDP-Based Planning 2 worst-case analysis for nondeterministic model 3 Expected Cost Planning 4 Real Time Dynamic Programming&#xff08;RTDP&#xff09; 1 什么是MDP-Based Planning 之前我们从起点到终点存在很多可执行路径&#xff0c;我们可以…

Python实现一箭穿心

文章目录 &#x1f384;效果&#x1f3f3;️‍&#x1f308;Turtle模块&#x1f339;代码&#x1f33a;代码讲解 &#x1f384;效果 &#x1f3f3;️‍&#x1f308;Turtle模块 Turtle是一个绘图工具&#xff0c;是Python标准库中的一个模块。它提供了一种简单而直观的方式来创…

【C++】内存管理(new与delete)

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章我们一起来学习C的内存管理方式&…

最新yolov8环境搭建、推理训练一站式超详细教学

1、获取yolov8源码 访问yolov8_github官网&#xff0c;网络不稳定时可能需要加速器。yolov8源码地址 获取方式&#xff1a;直接下载或者git工具克隆 我使用git操作进行演示&#xff0c;复制github上的地址(需提前关闭加速器)。 git clone https://github.com/ultralytics/ul…