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

news2024/11/15 8:18:08

一言

已知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];//边入选
            }
        }
        //统计并打印“最小生成树”,输出rets
        for (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,便于输出边
    @Override
    public String toString() {
        return "EData{" +
                "<" + start +
                "," + end +
                "> = " + weight +
                '}';
    }
}

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

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

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

相关文章

写给Java/Android开发者的Python入门教程

1. 前言 对于Java/Android开发工程师来说&#xff0c;已经掌握了Java语言&#xff0c;这时再学其他语言(C/C除外)&#xff0c;都是比较容易的&#xff0c;可能花上几个小时就能入门了。 作为一个Android开发工程师&#xff0c;今天一时兴起&#xff0c;学了下Python&#xff0…

初识进程以及父子进程

一 进程概念 什么是进程呢&#xff1f;许多资料都说一个已经加载到内存的程序就叫进程&#xff0c;意思是只要代码到了内存就能跑起来了吗?接下来我就谈谈对进程概念的理解。 1 如何管理进程 我们可能运行多个进程&#xff0c;这些进程有些结束&#xff0c;有些要退出&#x…

yolov5自己的数据集制作

文章目录 一、制作数据集1、创建文件夹结构如下2、将之前的图片以及标注数据放入mydata文件夹3、新建一个mydata.yaml文件 二、基于数据集训练模型1、基于数据集训练模型2、开始根据制作好的数据集训练模型3、模型训练结束 三、部署模型 文章参考博主&#xff1a;风吹落叶花飘荡…

Power BI 傻瓜入门 6. 从动态数据源获取数据

本章内容将介绍 发现如何从关系数据库和非关系数据库中提取数据学习如何使用Power BI使用在线和实时数据源跨多个数据源应用分析服务使用Power BI通过静态和动态数据解决纠正措施 数据有时可能有点复杂。诚然&#xff0c;上传一个包含几个电子表格的文件&#xff0c;或者一个…

计算机中整数的补码表示及二进制数轮

为了同学们能理好的理解数在计算机内的表示&#xff0c;我们可以把计算机中的整数看成N位进制数的数轮&#xff0c;N一般为2的幂&#xff0c;如下&#xff1a; 我们来举个例子&#xff1a;如果用4位二进制来表示整数&#xff0c;则可以表示的整数范围为-8&#xff08;即&#x…

详细介绍如何使用Ipopt非线性求解器求解带约束的最优化问题

本文中将详细介绍如何使用Ipopt非线性求解器求解带约束的最优化问题&#xff0c;结合给出的带约束的最优化问题示例&#xff0c;给出相应的完整的C程序&#xff0c;并给出详细的解释和注释&#xff0c;以及编译规则等 一、Ipopt库的安装和测试 本部分内容在之前的文章《Ubuntu2…

STM32-LTC6804方案成熟BMS方案

方案下载链接&#xff01;&#xff01;https://mp.weixin.qq.com/s?__bizMzU2OTc4ODA4OA&mid2247549092&idx1&snc73855c4e3d5afddd8608d8528864f95&chksmfcfb1373cb8c9a65a4bd1f545a1a587af882f209e7ccbb8944f4d2514d241ca1d7fcc4615e10&token539106225&a…

【字符函数】

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;进阶C语言 &#x1f388;相关博文&#xff1a;字符串函数&#xff08;一&#xff09;、字符串函数&#xff08;二&#xff09; 字符函数 字符函数1.字符分类函数1.1 iscntrl - 判断是否是控制字符1.2 i…

【GWO-KELM预测】基于灰狼算法优化核极限学习机回归预测研究(matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【Java基础面试四十六】、 List<? super T>和List<? extends T>有什么区别?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;问题 参考答案&#x…

蓝桥每日一题(day 4: 蓝桥592.门牌制作)--模拟--easy

#include <iostream> using namespace std; int main() {int res 0;for(int i 1; i < 2021; i ){int b i;while(b){if (b % 10 2) res ;b / 10;}}cout << res; return 0; }

FFmpeg和rtsp服务器搭建视频直播流服务

下面使用的是ubuntu的&#xff0c;window系统可以参考&#xff1a; 通过rtsp-simple-server和ffmpeg实现录屏并发布视频直播_rtsp simple server_病毒宇宇的博客-CSDN博客 一、安装rtsp-simple-server &#xff08;1&#xff09;下载rtsp-simple-server 下载地址&#xff1a;R…

搜索问答技术学习:基于知识图谱+基于搜索和机器阅读理解(MRC)

目录 一、问答系统应用分析 二、搜索问答技术与系统 &#xff08;一&#xff09;需求和信息分析 问答需求类型 多样的数据源 文本组织形态 &#xff08;二&#xff09;主要问答技术介绍 发展和成熟度分析 重点问答技术基础&#xff1a;KBQA和DeepQA KBQA&#xff08;…

Python高级技巧

十三、Python高级技巧 1. 闭包 解决全局变量问题&#xff1a; 代码在命名空间上&#xff08;变量定义&#xff09;不够干净、整洁全局变量又被修改的风险 定义&#xff1a; ​ 在函数嵌套的前提下&#xff0c;内部函数使用了外部函数的变量&#xff0c;并且外部函数返回了内部…

什么是内存泄漏,为什么threadlocal会造成内存泄漏?

内存泄漏&#xff1a;指的是应用程序中存在无用的对象或者资源没有被垃圾回收机制回收&#xff0c;从而导致内存占用不断增加&#xff0c;最终导致应用程序的崩溃。 jvm里对象的引用按照从强到弱&#xff0c;分为四个强&#xff0c;软&#xff0c;弱&#xff0c;虚。强引用不会…

YOLOv8改进实战 | 更换主干网络Backbone之PoolFormer篇

目录 一、PoolFormer二、代码实现2.1 添加PoolFormer网络2.2 注册PoolFormer网络2.3 配置yaml文件yolov8-PoolFormer.yaml2.3 模型验证2.4 模型训练三、总结一、PoolFormer 2022 CVPR 论文链接:MetaFormer Is Actually What You Need for Vision Pytorch code:poolformer

微信支付API

微信支付API 一、概念二、主要实现步骤 一、概念 主要经过小程序内调用登录接口、商户server调用支付统一下单、商户server调用再次签名&#xff0c;商户server接受支付通知&#xff0c;商户server查询支付结果。 二、主要实现步骤 1、小程序调用wx.login方法&#xff0c;获…

AD9371 官方例程之 tx_jesd 与 xcvr接口映射

文章目录 前言一、AD9371 ----> FMC_DP二、FMC_DP ----> FPGA_TX/RX三、rx_data_x and tx_data_x must be connected to the same channel四、ADRV9009 前言 axi_ad9371_tx_jesd --> util_ad9371_xcvr接口映射讲解 一、AD9371 ----> FMC_DP AD9371内部原理图 …

oracle实现搜索不区分大小写

<if test"code ! null and code ! ">and upper(code) like upper(%${code}%) </if>关键字upper

简单了解一下:NodeJS的fs文件系统

NodeJS提供了fs模块来本地文件。大致有这些内容&#xff1a; 文件读写 在操作文件之前&#xff0c;我们需要检查一下这个文件是否存在&#xff0c;fs模块提供了access方法&#xff0c;语法如下&#xff1a;fs.access(path,mode,callback)。 path&#xff1a;就是文件路径&…