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

一言

已知n个顶点,选n-1条最短的边,不可成环。

概述

克鲁斯卡尔(Kruskal)算法是用来求加权连通图的最小生成树的算法。其基本思想是按照权值从小到大的顺序选择n-1条边,保证这n-1条边不构成回路。
这就要求要首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。
也就是说:

  1. 要对边的权值进行排序;
  2. 不停加入新边且不能产生回路;

举个“栗”子

不妨从下面这个场景说起
在这里插入图片描述
郝乡长在光荣完成了德胜乡七个村子的修路任务之后,心情非常的好。因为他觉得之前悬而未决的交通巡检问题似乎也可以借鉴此前的宝贵经验。原来,得胜乡又七个集市(A-G),每逢过节都是人山人海,为了群众的安全,连贯的巡检是很必要的,可是如何设计连通七个集市的巡检路线呢?要短!要连贯!要高效!

图解

首先,不同的连接方式其权值总和也不同,如何找到最优解是关键。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
最后合龙,得到最优解
在这里插入图片描述
第1步: 将边<E,F>加入R 中边<E,F>的权值最小,因此将它加入到最小生成树结果 R 中
第2步: 将边<C,D>加入 R 中。上一步操作之后,边<C,D>的权值最小,因此将它加入到最小生成树结果 R 中
第3步: 将边<D,E>加入 R 中。上一步操作之后,边<D,E>的权值最小,因此将它加入到最小生成树结果 R 中
第4步: 将边<B,F>加入R 中。上一步操作之后,边<C,E>的权值最小,但<C,E>会和已有的边构成回路因此,跳过边<C,E>。同理,跳过边<C,F>。将边<B,F>加入到最小生成树结果R中。
第5步: 将边<E,G>加入 R 中。上一步操作之后,边<E,G>的权填最小,因此将它加入到最小生成树结果 R中
第6步: 将边<A,B>加入R 中。上一步操作之后,边<F,G>的权值最小,但<F,G>会和已有的边构成回路:因此,跳过边<F,G>。同理,跳过边<B,C>。将边<A,B>加入到最小生成树结果R中。

分析

根据前面介绍的克鲁斯卡尔算法的基本思想和做法,我们能够了解到,克鲁斯卡尔算法重点需要解决的以下两个问题:

  1. 对图的所有边按照权值大小进行排序。
  2. 将边添加到最小生成树中时,怎么样判断是否形成了回路。
    对于1 ,很好解决,采用排序算法进行排序即可。
    对于2,可以记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点”。然后每次需要将一条边添加到最小生存树时判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

如何判断回路

我们假定C->D->E->F连通,则此四个顶点实际都有终点:
C->F
D->F
E->F
F->F
终点的概念实际上就是避免二次访问,最小生成树要求每个点到另一个点都只有一种可能。以上例再加入CE边为例,在CE加入之前,从C到E已经有了CD->DE的方案,那么就不允许再引入CE这个方案。采用终点验证的方法实际上是把这个思路进行了归纳扩展。

代码实现

public class KruskalCase {private int edgeNum;//边的个数private char[]vertexs;//顶点数组private int[][]matrix;//邻接矩阵private static final int INF = Integer.MAX_VALUE;//使用INF表示两个顶点不能连同public static void main(String[] args) {//测试char[] vertexs = {'A','B','C','D','E','F','G'};int matrix[][] = {/*A* *B*  *C*  *D* *E* F  *G* *//*A*/  {0  ,12 ,INF,INF,INF,16 ,14 },/*B*/  {12 ,0  ,10 ,INF,INF,7  ,INF},/*C*/  {INF,10 ,0  ,3  ,5  ,6  ,INF},/*D*/  {INF,INF,3  ,0  ,4  ,INF,INF},/*E*/  {INF,INF,5  ,4  ,0  ,2  ,8  },/*F*/  {16 ,7  ,6  ,INF,2  ,0  ,9  },/*G*/  {14 ,INF,INF,INF,8  ,9  ,0  }};//创建KruskalCase 对象实例KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);//输出构建的kruskalCase.print();//        EData[] edges = kruskalCase.getEdges();
//        System.out.println("排序前:"+Arrays.toString(edges));//未排序
//        kruskalCase.sortEdges(edges);//排序
//        System.out.println("排序后:"+Arrays.toString(edges));//排序后kruskalCase.kruskal();}//构造器public KruskalCase(char[] vertexs, int[][] matrix) {//初始化顶点数和边个数int vlen = vertexs.length;//初始化顶点this.vertexs = vertexs;//初始化边this.matrix = matrix;//统计边的条数for (int i = 0; i < vlen; i++) {for (int j = i+1; j < vlen; j++) {if (this.matrix[i][j]!=INF){edgeNum++;}}}}//克鲁斯卡尔算法核心public void kruskal(){int index =0;//表示最后结果数组的索引int [] ends = new int[edgeNum];//用于保存“已有最小生成树”中的每个顶点在最小生成树中的终点//创建结果数组,保存最后的最小生成树EData [] rets = new EData[edgeNum];//获取图中所有的边的集合,一共有12条边EData[] edges = getEdges();System.out.println("图的边的集合:"+Arrays.toString(edges)+"。共"+edges.length+"条边。");//12//按照边的权值大小进行排序(从小到大)sortEdges(edges);//遍历edges数组,将边添加到最小生成树,判断准备加入的边是否构成回路,如果没有就加入rets,否则不能加入for (int i = 0; i < edgeNum; i++) {//获取第i条边的第1个顶点int p1 = getPosition(edges[i].start); // 比如边<E,F>, p1为4//获取第i条边的第2个顶点int p2 = getPosition(edges[i].end);   // 比如边<E,F>, p2为5//获取p1这个顶点在已有的最小生成树中的终点int m = getEnd(ends,p1);              //在上面的比如中,m=4//获取p2这个顶点在已有的最小生成树中的终点int n = getEnd(ends,p2);            //在上面的比如中,n=5//是否构成回路if (m!=n){//不构成回路//设置m在已有“最小生成树”中的终点。比如第一次:<E,F> [0,0,0,0,0,0,0,0,0,0,0,0] => [0,0,0,0,5,0,0,0,0,0,0,0]//对end数组的理解:第4位值为5,表示第4个顶点(E)的终点是第5个顶点(F)ends[m] = n;rets[index++] = edges[i];//边入选}}//统计并打印“最小生成树”,输出retsfor (int i = 0; i < index; i++) {System.out.println("最小生成树为=" + rets[i]);}}//打印邻接矩阵public void print(){System.out.println("邻接矩阵为:\n");for (int i = 0; i < vertexs.length; i++) {for (int j = 0; j < vertexs.length; j++) {System.out.printf("%12d\t",matrix[i][j]);}System.out.println();}}//边的排序(按权值),冒泡排序/*** @param edges 边的集合*/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;}}} }/*** @param ch 顶点的值, ‘A’,‘B’等* @return 返回ch顶点对应的下标,如果找不到返回-1*/private int getPosition(char ch){for (int i = 0; i < vertexs.length; i++) {if (vertexs[i]==ch)return i;}return -1;}/*** 获取图中的边,放到EData[]数组中,后面我们需要遍历该数组,通过matrix邻接矩阵来获取* EData[]形式 [['A','B',12],['B','F',7],......]* @return*/private EData[] getEdges(){int index = 0 ;EData[] edges = new EData[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;}/*** 获取下标为i的顶点的终点(),用于后面判断两个顶点的终点是否相同* @param ends 记录了各个顶点对应的终点是哪个,在遍历过程中逐步生成* @param i 传入的顶点对应的下标* @return 下标为i的这个顶点对应的终点的下标*/private int getEnd(int[] ends,int i){while (ends[i]!=0){i = ends[i];//有终点返回终点}return i;//未加入包含这个顶点的边,理解为终点是自己}
}//创建一个EData,它的对象实例就表示一条边
class EData{char start; //边的一个点(起点)char end;//边的另一点(终点)int weight;//边的权//构造器public EData(char start, char end, int weight) {this.start = start;this.end = end;this.weight = weight;}//重写toString,便于输出边@Overridepublic String toString() {return "EData{" +"<" + start +"," + end +"> = " + weight +'}';}
}

关注我,共同进步,每周至少一更。——Wayne

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/114618.shtml

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

相关文章

一百九十一、Flume——Flume配置文件各参数含义(持续完善中)

一、目的 在实际项目的开发过程中&#xff0c;不同Kafka主题的数据规模、数据频率&#xff0c;需要配置不同的Flume参数&#xff0c;而这一切的调试、配置工作&#xff0c;都要建立在对Flume配置文件各参数含义的基础上 二、Flume各参数及其含义 &#xff08;一&#xff09;…

集成学习方法(随机森林和AdaBoost)

释义 集成学习很好的避免了单一学习模型带来的过拟合问题 根据个体学习器的生成方式&#xff0c;目前的集成学习方法大致可分为两大类&#xff1a; Bagging(个体学习器间不存在强依赖关系、可同时生成的并行化方法) 流行版本&#xff1a;随机森林(random forest)Boosting(个体…

springboot缓存篇之mybatis一级缓存和二级缓存

前言 相信很多人都用过mybatis&#xff0c;这篇文章主要是介绍mybatis的缓存&#xff0c;了解一下mybatis缓存是如何实现&#xff0c;以及它在实际中的应用 一级缓存 什么是mybatis一级缓存&#xff1f;我们先看一个例子&#xff1a; GetMapping("/list") public…

【Mysql】B+树索引的使用(七)

前言 每个索引都对应一棵 B 树&#xff0c; B 树分为多层&#xff0c;最下边一层是叶子节点&#xff0c;其余的是内节点&#xff08;非叶子节点&#xff09;。所有用户记录都存储在 B 树的叶子节点&#xff0c;所有目录项记录都存储在内节点。 InnoDB 存储引擎会自动为主键&am…

Node学习笔记之包管理工具

一、概念介绍 1.1 包是什么 『包』英文单词是package &#xff0c;代表了一组特定功能的源码集合 1.2 包管理工具 管理『包』的应用软件&#xff0c;可以对「包」进行 下载安装 &#xff0c; 更新 &#xff0c; 删除 &#xff0c; 上传 等操作 借助包管理工具&#xff0c;可…

推理引擎之模型压缩浅析

目录 前言1. 模型压缩架构和流程介绍2. 低比特量化原理2.1 量化基础介绍2.2 量化方法2.3 量化算法原理2.4 讨论 3. 感知量化训练QAT原理3.1 QAT原理3.2 量化算子插入3.3 QAT训练流程3.4 QAT衍生研究3.5 讨论 4. 训练后量化PTQ4.1 动态PTQ4.2 静态PTQ4.3 KL散度实现静态PTQ4.4 量…

最详细STM32,cubeMX 定时器

这篇文章将详细介绍 STM32,cubeMX 定时器的配置和使用。 文章目录 前言一、定时器基础知识二、cubeMX 配置三、定时时长四、自动生成代码讲解五、实验程序总结 前言 实验开发板&#xff1a;STM32F103C8T6。所需软件&#xff1a;keil5 &#xff0c; cubeMX 。实验目的&#xff…

无人机UAV目标检测与跟踪(代码+数据)

前言 近年来&#xff0c;随着无人机的自主性、灵活性和广泛的应用领域&#xff0c;它们在广泛的消费通讯和网络领域迅速发展。无人机应用提供了可能的民用和公共领域应用&#xff0c;其中可以使用单个或多个无人机。与此同时&#xff0c;我们也需要意识到无人机侵入对空域安全…

牛客:NC59 矩阵的最小路径和

牛客&#xff1a;NC59 矩阵的最小路径和 文章目录 牛客&#xff1a;NC59 矩阵的最小路径和题目描述题解思路题解代码 题目描述 题解思路 动态规划&#xff0c;递推公式&#xff1a;matrix[i][j] min(matrix[i-1][j], matrix[i][j-1]) 题解代码 func minPathSum( matrix [][…

【数据科学赛】2023全球智能汽车AI挑战赛 #¥95000 #LLM文档问答 #视频理解

CompHub[1] 最新的比赛会第一时间在群里通知&#xff0c;欢迎加群交流比赛经验&#xff01;&#xff08;公众号回复“加群”即可&#xff09; 以下内容由AI辅助生成&#xff0c;可能存在错误&#xff0c;可进入比赛主页[2]查看更多(文末阅读原文) 比赛主办方 吉利汽车集团、阿…

【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载

拷贝构造函数 概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称其为双胞胎 那在创建对象时&#xff0c;可否创建一个与已存在对象一某一样的新对象呢&#xff1f; 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用…

FPGA的斐波那契数列Fibonacci设计verilog,代码和视频

名称&#xff1a;斐波那契数列Fibonacci设计verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 设计一个产生斐波那契数列&#xff08;也叫黄金分割数列&#xff09;的硬件电路: 斐波那契数列中每个数为其相邻前两个数的和:即FNFN1FN2,(数列…

Python:函数篇(每周练习)

编程题&#xff1a; Python第四章作业&#xff08;初级&#xff09; (educoder.net) 题一&#xff1a;无参无返回值函数 def print_hi_human(): # 函数名用小写字母print("人类&#xff0c;你好&#xff01;")if __name__ __main__:print_hi_human() 题二&#…

设计模式篇---组合模式

文章目录 概念结构实例总结 概念 组合模式&#xff1a;组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。 当我们开发中遇到树形结构的业务时&#xff0c;可以考虑使用组合模式。&#xff08;我也没有想明白为啥…

Mysql数据库 2.SQL语言 数据类型与字段约束

Mysql数据类型 数据类型&#xff1a;指的是数据表中的列文件支持存放的数据类型 1.数值类型 Mysql当中有多种数据类型可以存放数值&#xff0c;不同的类型存放的数值的范围或者形式是不同的 注&#xff1a;前三种数字类型我们在实际研发中用的很少&#xff0c;一般整数类型…

空中计算(Over-the-Air Computation)学习笔记

文章目录 写在前面 写在前面 本文是论文A Survey on Over-the-Air Computation的阅读笔记&#xff1a; 通信和计算通常被视为独立的任务。 从工程的角度来看&#xff0c;这种方法是非常有效的&#xff0c;因为可以执行孤立的优化。 然而&#xff0c;对于许多面向计算的应用程序…

游戏反虚拟框架检测方案

游戏风险环境&#xff0c;是指独立于原有设备或破坏设备原有系统的环境。常见的游戏风险环境有&#xff1a;iOS越狱、安卓设备root、虚拟机、虚拟框架、云手机等。 因为这类风险环境可以为游戏外挂、破解提供所需的高级别设备权限&#xff0c;所以当游戏处于这些设备环境下&am…

ARM可用的可信固件项目简介

安全之安全(security)博客目录导读 目录 一、TrustedFirmware-A (TF-A) 二、MCUboot 三、TrustedFirmware-M (TF-M) 四、TF-RMM 五、OP-TEE 六、Mbed TLS 七、Hafnium 八、Trusted Services 九、Open CI 可信固件为Armv8-A、Armv9-A和Armv8-M提供了安全软件的参考实现…

【UE5】 ListView使用DataTable数据的蓝图方法

【UE5】 ListView使用DataTable数据的蓝图方法 ListView 是虚幻引擎中的一种用户界面控件&#xff0c;用于显示可滚动的列表。它可以用于显示大量的数据&#xff0c;并提供了各种功能和自定义选项来满足不同的需求。 DataTable是虚幻引擎中的一种数据表格结构&#xff0c;用于存…

Vue Router - 路由的使用、两种切换方式、两种传参方式、嵌套方式

目录 一、Vue Router 1.1、下载 1.2、基本使用 a&#xff09;引入 vue-router.js&#xff08;注意&#xff1a;要在 Vue.js 之后引入&#xff09;. b&#xff09;创建好路由规则 c&#xff09;注册到 Vue 实例中 d&#xff09;展示路由组件 1.3、切换路由的两种方式 1.…