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

七.克鲁斯卡尔(Kruskal)算法

文章目录

    • 克鲁斯卡尔算法
      • 应用场景—公交站问题
      • 克鲁斯卡尔算法介绍
      • 克鲁斯卡尔算法图解说明
        • 克鲁斯卡尔算法分析
        • 如何判断是否构成回路—举例说明
      • 代码实现
        • 第一步 先构建图
        • 最终代码

克鲁斯卡尔算法

应用场景—公交站问题

看一个应用场景和问题

image-20250818174031232

1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通

2)各个站点的距离用边线表示(权),比如A—B距离12公里

3)问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?

克鲁斯卡尔算法介绍

1)克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法

2)基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路

3)具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。

克鲁斯卡尔算法图解说明

以城市公交站问题来图解说明 克鲁斯卡尔算法的原理和步骤:

image-20250818223420587

image-20250818223502589

image-20250818223555342

image-20250818223633124

image-20250818223719274

image-20250818223814491

此时,最小生成树构造完成,它包括的边依次是:<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>

克鲁斯卡尔算法分析

克鲁斯卡尔算法重点需要解决以下两个问题:

问题一:对图的所有边按照权值大小进行排序。

问题二:将边添加到最小生成树时,怎么判断是否形成了回路

问题一很好解决,采用排序算法进行排序即可。

问题二处理方式是:记录顶点在“最小生成树”中的终点,顶点的终点是“在最小生成树中与它连通的最大顶点”。然后每次需要将一条边添加到最小生成树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

如何判断是否构成回路—举例说明

image-20250818230023880

在将<E,F> <C,D> <D,E>加入到最小生成树R中之后,这几条边的顶点就都有了终点:

(1) C的终点是F

(2) D的终点是F

(3) E的终点是F

(4) F的终点是F

关于终点的说明:就是将所有顶点按照从小到大的顺序排列好之后,某个顶点的终点就是“与它连通的最大顶点”。因此,虽然<C,E>是权值最小的边,但是C和E得终点都是F,即它们的终点相同。因此,将<C,E>加入最小生成树的话,会形成回路。这就是判断回路的方式。也就是说,我们加入的边的两个顶点不能都指向同一个终点,否则将构成回路。(后面有代码说明)

代码实现

第一步 先构建图
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace KruskalCase
{class Kruskal{private int edgeNum;   //记录边的个数private char[] vertexs;  //顶点数组private int[,] matrix;  //存储图的邻接矩阵private static int INF = 999;    //使用999表示两个顶点不能连通static void Main(string[] args){char[] vertexs = { 'A', 'B', 'C', 'D', 'E','F', 'G' };  //七个顶点// 邻接矩阵  0表示相同的点间的距离, INF表示不是直连int[,] matrix ={{0,12,INF,INF,INF,16,14 },{12,0,10,INF,INF,7,INF },{INF,10,0,3,5,6,INF},{INF,INF,3,0,4,INF,INF },{INF,INF,5,4,0,2,8 },{16,7,6,INF,2,0,9   },{14,INF,INF,INF,8,9,0 }};//创建Kruskal对象实例Kruskal kruskal = new Kruskal(vertexs, matrix);//输出kruskal.print();}//构造器public Kruskal(char[] vertexs,int[,] matrix){//初始化顶点数int vlen = vertexs.Length;//初始化顶点,复制拷贝的方式this.vertexs = new char[vlen];for (int i = 0; i < vertexs.Length; i++){this.vertexs[i] = vertexs[i];}//初始化边,使用的是复制拷贝的方式this.matrix = new int[vlen, vlen];for (int i = 0; i < vlen; i++){for (int j = 0; j < vlen; j++){this.matrix[i, j] = matrix[i, j];}}//统计边,看看到底有多少条边for (int i = 0; i < vlen; i++){for (int j = 0; j < vlen; j++){if (this.matrix[i, j] != INF){edgeNum++;}}}}//打印邻接矩阵public void print(){Console.WriteLine("邻接矩阵为:\n");for (int i = 0; i < vertexs.Length; i++){for (int j = 0; j < vertexs.Length; j++){Console.Write("{0,5}"+matrix[i,j]);}Console.WriteLine();  //换行}}}
}
最终代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace KruskalCase
{class Kruskal{private int edgeNum;   //记录边的个数private char[] vertexs;  //顶点数组private int[,] matrix;  //存储图的邻接矩阵private static int INF = 999;    //使用999表示两个顶点不能连通static void Main(string[] args){char[] vertexs = { 'A', 'B', 'C', 'D', 'E','F', 'G' };  //七个顶点// 邻接矩阵  0表示相同的点间的距离, INF表示不是直连int[,] matrix ={{0,12,INF,INF,INF,16,14 },{12,0,10,INF,INF,7,INF },{INF,10,0,3,5,6,INF},{INF,INF,3,0,4,INF,INF },{INF,INF,5,4,0,2,8 },{16,7,6,INF,2,0,9   },{14,INF,INF,INF,8,9,0 }};//创建Kruskal对象实例Kruskal kruskal = new Kruskal(vertexs, matrix);//打印邻接矩阵kruskal.print();//EData[] edges = kruskal.getEdges();////把图中的边打印出来,排序前//Console.WriteLine("排序前:");//foreach (var item in edges)//{//    Console.Write(item+"  ");//}//Console.WriteLine("\n"); //换行//Console.WriteLine("排序后:");//kruskal.sortEdges(edges);  ////对边进行排序后,把边打印出来//foreach (var item in edges)//{//    Console.Write(item+"  ");//}kruskal.kruskalalgorithm();}//构造器public Kruskal(char[] vertexs,int[,] matrix){//初始化顶点数int vlen = vertexs.Length;//初始化顶点,复制拷贝的方式this.vertexs = new char[vlen];for (int i = 0; i < vertexs.Length; i++){this.vertexs[i] = vertexs[i];}//初始化边,使用的是复制拷贝的方式this.matrix = new int[vlen, vlen];for (int i = 0; i < vlen; i++){for (int j = 0; j < vlen; j++){this.matrix[i, j] = matrix[i, j];}}//统计边的条数,看看到底有多少条边for (int i = 0; i < vlen; i++){for (int j = i+1; j < vlen; j++){if (this.matrix[i, j] != INF ){edgeNum++;}}}}//克鲁斯卡尔算法得到最小生成树public void kruskalalgorithm(){int index = 0; //表示最后结果数组的索引int[] ends = new int[edgeNum];  //用于保存"已有最小生成树"中的每个顶点在最小生成树中的终点//创建结果数组,保存最后的最小生成树EData[] rets = new EData[edgeNum];//获取图中所有的边的集合,一共有12条边EData[] edges = getEdges();Console.WriteLine("图的边的集合:");foreach (var item in edges){Console.WriteLine(item+"  ");}Console.WriteLine("共有:"+edges.Count()+"条边");  //12Console.WriteLine();  //首先按照边的权值大小进行排序(从小到大)sortEdges(edges);//遍历edges数组,将边添加到最小生成树中时,判断准备加入的边是否形成了回路,如果没有,就加入rets,否则不能加入for(int i = 0; i < edgeNum; i++){//获取到第i条边的第一个顶点(起点)int p1 = getPosition(edges[i].start);  //p1=4//获取到第i条边的第二个顶点int p2 = getPosition(edges[i].end);  //p2=5//获取p1这个顶点在已有的最小生成树中的终点int m = getEnd(ends, p1); //m=4 未加入的顶点,终点就是它本身//获取p2这个顶点在已有的最小生成树中的终点int n = getEnd(ends, p2);  //n=5//是否构成回路if (m != n)  //没有构成回路{ends[m] = n;  //设置m在"已有最小生成树"中的终点为n,以边<E,F>举例,E的终点是Frets[index++] = edges[i];   //有一条边加入到rets数组}}//打印“最小生成树”Console.WriteLine("最小生成树为:");foreach (var item in rets){Console.WriteLine(item+"  ");}}//打印邻接矩阵public void print(){Console.WriteLine("邻接矩阵为:\n");for (int i = 0; i < vertexs.Length; i++){for (int j = 0; j < vertexs.Length; j++){Console.Write("{0,5}",matrix[i,j]);  //5表示5个占位符,-表示从左侧对齐,没有负号表示从右侧对齐}Console.WriteLine();  //换行}     }/// <summary>/// 对边进行排序处理,冒泡排序(对图的所有边按照权值大小进行排序。)/// </summary>/// <param name="edges">边的集合</param>private void sortEdges(EData[] edges){for (int i = 0; i < edges.Length-1; i++){for (int j = 0; j < edges.Length-1-i; j++){if (edges[j].weight > edges[j + 1].weight){//交换EData tmp = edges[j];edges[j] = edges[j + 1];edges[j + 1] = tmp;}}}}/// <summary>/// 获取顶点的下标/// </summary>/// <param name="ch">顶点的值,比如'A','B'</param>/// <returns>返回ch顶点对应的下标,如果找不到,返回-1</returns>private int getPosition(char ch){for (int i = 0; i < vertexs.Length; i++){ if (vertexs[i] == ch) //找到{return i;}}//找不到,返回-1return -1;}/// <summary>/// 获取图中的边,放到EData[]数组中,后面我们需要遍历该数组/// 是通过matrix 邻接矩阵来获取边的权值/// EData[]形式 [['A','B',12], ['B','F',7],...]/// </summary>/// <returns></returns>private EData[] getEdges(){int index = 0;EData[] edges = new EData[edgeNum];  //edgeNum:边的条数for (int i = 0; i < vertexs.Length; i++){for (int j =i+1; j < vertexs.Length; j++){if (matrix[i, j] != INF){edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i, j]);}}}return edges;}/// <summary>/// 功能:获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相同,用于判断权值最小的边是否能加入到最小生成树的方法/// </summary>/// <param name="ends">ends数组就是记录了各个顶点对应的终点下标是哪个,ends数组是在遍历过程中,逐步形成的</param>/// <param name="i">传入的顶点对应的下标</param>/// <returns>返回的就是下标为i的这个顶点对应的终点的下标</returns>private int getEnd(int[] ends,int i){while (ends[i] != 0) //哪个顶点的终点是哪一个是动态加入的(如果终点不等于零则返回终点,如果终点等于零则返回自己下标){i = ends[i];}return i;}}//创建一个类EData,它的对象实例就表示一条边class EData{public char start;   //边的一个点public char end;    //边的另外一个点public int weight;   //边的权值//构造器public EData(char start,char end,int weight){this.start = start;this.end = end;this.weight = weight;}//重写Tostring方法,便于输出边public override string ToString(){return "EData  [start="+start+ ",  end="+end +" ,weight="+weight+"]";}}
}

文章转载自:

http://n2P4YRLU.ypktc.cn
http://sD0tSACX.ypktc.cn
http://q7NdgBS3.ypktc.cn
http://KImgDCtm.ypktc.cn
http://neK2yeGr.ypktc.cn
http://zEPCSxMn.ypktc.cn
http://4mVaAS3q.ypktc.cn
http://avjafG1z.ypktc.cn
http://iTTmsBdg.ypktc.cn
http://M0NAT8Tx.ypktc.cn
http://93hjLwDZ.ypktc.cn
http://v3BAP5hk.ypktc.cn
http://tbYIjLiZ.ypktc.cn
http://s7svozUE.ypktc.cn
http://LsOoFplk.ypktc.cn
http://yle5RG03.ypktc.cn
http://SaEAIDKD.ypktc.cn
http://G40TXkBF.ypktc.cn
http://HRIwNzgv.ypktc.cn
http://brhsCLis.ypktc.cn
http://J85UuF1Q.ypktc.cn
http://CTULbQZ5.ypktc.cn
http://RM9r26dW.ypktc.cn
http://iEuMD863.ypktc.cn
http://zzpx2sLG.ypktc.cn
http://s0pl7D8T.ypktc.cn
http://qpHd65oz.ypktc.cn
http://Xcl177rw.ypktc.cn
http://UIdQUJLx.ypktc.cn
http://HBf3ivPs.ypktc.cn
http://www.dtcms.com/a/372385.html

相关文章:

  • 区块链—NFT介绍及发行
  • JavaSSM框架-MyBatis 框架(一)
  • c6-类和对象-对象特征-初始化列表
  • ThermoSeek:热稳定蛋白数据库
  • 不同Autosar CAN版本的主要实现差异
  • Jakarta EE课程扩展阅读(二)
  • 算法模板(Java版)
  • 【多模态学习】QA2:Tokenize和Embedding?BPE算法?交叉熵损失函数?
  • ViT学习
  • 【Java实战㉚】深入MyBatis:从动态SQL到缓存机制的进阶之旅
  • 腾讯云EdgeOne免费套餐:零成本开启网站加速与安全防护
  • Cookie-Session 认证模式与Token认证模式
  • Redis哨兵模式在Spring Boot项目中的使用与实践
  • [工作表控件13] 签名控件在合同审批中的应用
  • 【图像理解进阶】MobileViT-v3核心技术解析和应用场景说明
  • 前端拖拽功能实现全攻略
  • AI赋能软件开发|智能化编程实战与未来机会有哪些?
  • 335章:使用Scrapy框架构建分布式爬虫
  • Docker|“ssh: connect to host xxx.xxx.xxx.xxx port 8000: Connection refused“问题解决
  • OneCode 可视化揭秘系列(三):AI MCP驱动的智能工作流逻辑编排
  • 数据结构深度解析:二叉树的基本原理
  • Supabase02-速通
  • LLM学习:大模型基础——视觉大模型以及autodl使用
  • 嵌入式Secure Boot安全启动详解
  • 【倍增】P3901 数列找不同|普及+
  • 数据结构:堆
  • 继续优化基于树状数组的cuda前缀和
  • 数组常见算法
  • 数仓建模理论
  • 致远A8V5 9.0授权文件