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.hqwc.cn/news/144666.html

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

相关文章

C++ STL六大组件

目录 前言 一、容器 1 向量 1.1 向量&#xff08;Vector&#xff09;和数组&#xff08;array&#xff09;之间的区别 1.2 语法 1.3 示例 1.3.1 创建 vector 对象 1.3.2 不能打印向量对象&#xff1b;不能打印空向量中的元素&#xff0c;因为空向量中无元素可打印 1.3…

PDF编辑阅读 PDF Expert v3.5.2

PDF Expert是由Readdle开发的一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 以下是PDF Expert的特点&#xff1a; PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动…

Spring Cloud Alibaba Seata 实现分布式事物

Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式&#xff0c;为用户打造一站式的分布式解决方案 Seata 官网&#xff1a;https://seata.io/zh-cn/ Spring Cloud Alibaba 官…

Java利用反射和读取xml实现迷你容器

由于需要框架能实现多态&#xff0c;达到控制反转解耦。所以容器还是需要的&#xff0c;容器的存在可以简化对象获取工作&#xff0c;但是容器也不是万能的。合理使用即可&#xff0c;Spring对我来说太庞大了&#xff0c;用不着&#xff0c;为此给框架写一个迷你版容器。 容器…

SpringCloud-Sentinel

一、介绍 &#xff08;1&#xff09;提供界面配置配置服务限流、服务降级、服务熔断 &#xff08;2&#xff09;SentinelResource的blockHandler只处理后台配置的异常&#xff0c;运行时异常fallBack处理&#xff0c;且资源名为value时才生效&#xff0c;走兜底方法 二、安装…

裸机与RTOS(概念、关系、区别)

目录 裸机 什么是裸机&#xff1f; 裸机开发的特点 STM32裸机开发 RTOS 什么是RTOS&#xff1f; RTOS技术的概念及特点 STM32中的RTOS 裸机开发与RTOS开发对比分析 裸机开发 RTOS开发 如何选择&#xff1f; 裸机 什么是裸机&#xff1f; 在嵌入式领域&#xff0c;…

UE5--物体卡片与材质入门

参考资料&#xff1a; 《Unreal Engine5 入门到精通》--左央 虚幻引擎5.2文档&#xff1a;https://docs.unrealengine.com/5.2/zh-CN/ 前言&#xff1a; 跟着左央老师的《Unreal Engine5 入门到精通》学习制作AI版胡闹厨房&#xff0c;把学习过程与学习到的东西归纳总结起来。 …

通用FIFO设计深度8宽度64,verilog仿真,源码和视频

名称&#xff1a;通用FIFO设计深度8宽度64&#xff0c;verilog仿真 软件&#xff1a;Quartus 语言&#xff1a;verilog 本代码为FIFO通用代码&#xff0c;其他深度和位宽可简单修改以下参数得到 reg [63:0] ram [7:0];//RAM。深度8&#xff0c;宽度64 代码功能&#xff1a…

存储优化知识复习二详细版解析

存储优化 知识复习二 一、 选择题 1、 对数据库调优的方法中&#xff0c;最困难但是最有成效的是( )。 A、优化表的架构设计 B、添加内存 C、索引优化 D、查询语句优化 【参考答案】A2、 防止与处理死锁的方法有&#xff08; &#xff09;。 A、尽量避免或尽快处理阻塞 B、访…

人工智能、机器学习、深度学习的区别

人工智能涵盖范围最广&#xff0c;它包含了机器学习&#xff1b;而机器学习是人工智能的重要研究内容&#xff0c;它又包含了深度学习。 人工智能&#xff08;AI&#xff09; 人工智能是一门以计算机科学为基础&#xff0c;融合了数学、神经学、心理学、控制学等多个科目的交…

【项目设计】网络对战五子棋(上)

想回家过年… 文章目录 一、项目前置知识1. websocketpp库1.1 http1.0/1.1和websocket协议1.2 websocketpp库接口的前置认识1.3 搭建一个http/websocket服务器 2. jsoncpp库3. mysqlclient库 二、 项目设计1. 项目模块划分2. 实用工具类模块2.1 日志宏封装2.2 mysql_util2.3 j…

1、VMware虚拟机及网络配置

一、VMware虚拟网络编辑器 1、选择NAT模式并配置子网 2、进入NAT设置&#xff0c;配置网关 3、宿主机网络适配器设置 二、创建虚拟机 在这里插入图片描述 三、开启虚拟机&#xff0c;安装操作系统 在该网段内配置静态ip&#xff0c;指定网关为前面NAT配置的网关地址…