【算法】克鲁斯卡尔 (Kruskal) 算法

news2024/12/27 17:35:11

目录

  • 1.概述
  • 2.代码实现
    • 2.1.并查集
    • 2.2.邻接矩阵存储图
    • 2.3.邻接表存储图
    • 2.4.测试代码
  • 3.应用

本文参考:
《数据结构教程》第 5 版 李春葆 主编

1.概述

(1)在一给定的无向图 G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边,而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集且为无循环图,使得联通所有结点的 w(T) 最小,则此 T 为 G 的最小生成树 (minimal spanning tree)

(2)克鲁斯卡尔 (Kruskal) 算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。假设 G = (V, E) 是一个具有 n 个顶点的带权连通无向图,T = (U, TE) 是 G 的最小生成树,则构造最小生成树的步骤如下:

  • 置 U 的初值为 V(即包含有 G 中的全部顶点),TE 的初值为空集(即图 T 中的每一个顶点都构成一个分量);
  • 将图 G 中的边按权值从小到大的顺序依次选取,若选取的边未使生成树 T 形成回路,则加入 TE,否则将其舍弃,直到 TE 中包含 (n - 1) 条边为止;

(3)例如,对带权连通无向图 G 使用克鲁斯卡尔 (Kruskal) 算法构造最小生成树的过程如下:

在这里插入图片描述

2.代码实现

2.1.并查集

在使用克鲁斯卡尔 (Kruskal) 算法来构造最小生成树时需要判断选择的边是否使树 T 形成回路,并还涉及到连通分量的合并,因此这里选择使用并查集来解决这个问题。有关并查集的具体知识可查看【数据结构】并查集这篇文章,并查集的代码实现如下:

//并查集
class UnionFind {
    //记录连通分量(树)的个数
    private int count;
    //节点 x 的根节点是 root[x]
    private int[] root;
    
    //构造函数
    public UnionFind(int n) {
        //初始时每个节点都是一个连通分量
        this.count = n;
        root = new int[n];
        //初始时每个节点的根节点都是其自己,即每棵树中只有一个节点
        for (int i = 0; i < n; i++) {
            root[i] = i;
        }
    }
    
    //将 p 和 q 连通
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) {
            // p 和 q 的根节点相同,它们本就是连通的,直接返回即可
            return;
        } else {
            root[rootQ] = rootP;
            // 两个连通分量合并成一个连通分量
            count--;
        }
    }
    
    //判断 p 和 q 是否互相连通,即判断 p 和 q 是否在同一颗树中
    public boolean isConnected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        //如果 p 和 q 的根节点相同,则说明它们在同一颗树中,即它们是连通的
        return rootP == rootQ;
    }
    
    //查找节点 x 的根节点
    public int find(int x) {
        if (root[x] != x) {
            root[x] = find(root[x]);
        }
        return root[x];
    }
    
    //返回连通分量(树)的个数
    public int getCount() {
        return count;
    }
}

2.2.邻接矩阵存储图

class Solution {
    /**
     * @param1: 邻接矩阵
     * 			adjMatrix[i][j] = 0 表示节点 i 和 j 之间没有边直接相连;
     * 			adjMatrix[i][j] = weight > 0 表示节点 i 和 j 之间的边的权值;
     * @return: Kruskal 算法依次选择的边权值以及该边连接的两个节点
     * @description: 使用 Kruskal 算法得到最小生成树
     */
    public List<int[]> kruskal(int[][] adjMatrix) {
        //图的节点数
        int n = adjMatrix.length;
        // edges 保存所有边及权重,int[] 中存储 {节点 i, 节点 j, i 和 j 之间的边的权值}
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (adjMatrix[i][j] != 0) {
                    edges.add(new int[]{i, j, adjMatrix[i][j]});
                }
            }
        }
        //将边按照权重进行升序排序
        Collections.sort(edges, Comparator.comparingInt(a -> a[2]));
        //最小生成树中所有边的权值之和
        int weightSum = 0;
        //保存 Kruskal 算法中依次选择的边权值以及该边连接的两个节点
        List<int[]> infos = new ArrayList<>();
        UnionFind uf = new UnionFind(n);
        //依次遍历排序后的每一条边
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            int weight = edge[2];
            //选中的边会产生环,则不能将其加入最小生成树中
            if (uf.isConnected(u, v)) {
                continue;
            }
            //如果选中的边不会产生环,则它属于最小生成树
            weightSum += weight;
            //将节点 u 和 v 进行连通
            uf.union(u, v);
            infos.add(new int[]{u, v, weight});
        }
        System.out.println("最小生成树中边的权值之和:" + weightSum);
        return infos;
    }
}

2.3.邻接表存储图

class Solution {
    /**
     * @param1: 邻接表,List<int[]> nodes = adjList[i] 是一个 list,其中:
     *          nodes.get(j)[0] 表示与节点 i 相邻的第 j 个节点的编号;
     *          nodes.get(j)[1] 表示节点 i 与节点 nodes.get(j)[0] 之间的边权值;
     * @param1: 图的节点数
     * @return: kruskal 算法依次选择的边权值以及该边连接的两个节点
     * @description: 使用 kruskal 算法得到最小生成树
     */
    public static List<int[]> kruskal(List<int[]>[] adjList, int n) {
        // edges 保存所有边及权重,int[] 中存储 {节点 i, 节点 j, i 和 j 之间的边的权值}
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < adjList.length; i++) {
            // nodes 存储与节点 i 相邻的节点信息
            List<int[]> nodes = adjList[i];
            //遍历每个与节点 i 相邻的节点
            for (int[] node : nodes) {
                //节点 i 与 节点 v 相邻
                int v = node[0];
                // weight 为 i、v 之间的边的权值
                int weight = node[1];
                edges.add(new int[]{i, v, weight});
            }
        }
        //将边按照权重进行升序排序
        Collections.sort(edges, Comparator.comparingInt(a -> a[2]));
        //最小生成树中所有边的权值之和
        int weightSum = 0;
        //保存 Kruskal 算法中依次选择的边权值以及该边连接的两个节点
        List<int[]> infos = new ArrayList<>();
        UnionFind uf = new UnionFind(n);
        //依次遍历排序后的每一条边
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            int weight = edge[2];
            //选中的边会产生环,则不能将其加入最小生成树中
            if (uf.isConnected(u, v)) {
                continue;
            }
            //如果选中的边不会产生环,则它属于最小生成树
            weightSum += weight;
            //将节点 u 和 v 进行连通
            uf.union(u, v);
            infos.add(new int[]{u, v, weight});
        }
        System.out.println("最小生成树中边的权值之和:" + weightSum);
        return infos;
    }
}

2.4.测试代码

public static void main(String[] args) {
	//邻接矩阵
    int[][] adjMatrix = {
            {0, 9, 0, 0, 0, 1, 0},
            {9, 0, 4, 0, 0, 0, 3},
            {0, 4, 0, 2, 0, 0, 0},
            {0, 0, 2, 0, 6, 0, 5},
            {0, 0, 0, 6, 0, 8, 7},
            {1, 0, 0, 0, 8, 0, 0},
            {0, 3, 0, 5, 7, 0, 0}
    };
    List<int[]> nodes1 = kruskal(adjMatrix);
    for (int[] node : nodes1) {
        System.out.println(node[0] + " "+ node[1] + " " + node[2]);
    }
    
    //邻接表
    int n = 7;
    List<int[]>[] adjList = new ArrayList[n];
    for (int i = 0; i < n; i++) {
        adjList[i] = new ArrayList<>();
    }
    adjList[0].add(new int[]{1, 9});
    adjList[0].add(new int[]{5, 1});
    
    adjList[1].add(new int[]{0, 9});
    adjList[1].add(new int[]{2, 4});
    adjList[1].add(new int[]{6, 3});
    
    adjList[2].add(new int[]{1, 4});
    adjList[2].add(new int[]{3, 2});
    
    adjList[3].add(new int[]{2, 2});
    adjList[3].add(new int[]{4, 6});
    adjList[3].add(new int[]{6, 5});
    
    adjList[4].add(new int[]{3, 6});
    adjList[4].add(new int[]{5, 8});
    adjList[4].add(new int[]{6, 7});
    
    adjList[5].add(new int[]{0, 1});
    adjList[5].add(new int[]{4, 8});
    
    adjList[6].add(new int[]{1, 3});
    adjList[6].add(new int[]{3, 5});
    adjList[6].add(new int[]{4, 7});
    
//        List<int[]> nodes2 = kruskal(adjList, 7);
//        for (int[] node : nodes2) {
//            System.out.println(node[0] + " "+ node[1] + " " + node[2]);
//        }
}

结果如下:

最小生成树中边的权值之和:24
0 5 1
2 3 2
1 6 3
1 2 4
3 4 6
4 5 8

3.应用

(1)求图的最小生成树许多实际应用,例如城市之间的交通工程造价最优问题就是一个最小生成树问题。

(2)大家可以去 LeetCode 上找相关的最小生成树的题目来练习,或者也可以直接查看LeetCode算法刷题目录 (Java)这篇文章中的最小生成树章节。如果大家发现文章中的错误之处,可在评论区中指出。

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

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

相关文章

【6s965-fall2022】剪枝✂pruningⅠ

模型剪枝的介绍 修剪&#xff0c;消除不必要的知识。DNN的知识可以理解为存在于其权重中。 事实证明&#xff0c;许多 DNN 模型可以被分解为权重张量&#xff0c;而权重张量经常包含统计冗余&#xff08;稀疏性&#xff09;。因此&#xff0c;你可以压缩 DNN 的权重张量&…

[从零开始]用python制作识图翻译器·五

测试 通过以上步骤我们终于实现了系统&#xff0c;现在到了紧张刺激的测试环节。直接运行run.py文件: python run.py ::注意需要进入conda环境稍作等等&#xff0c;我们的系统就运行啦&#xff08;啵唧啵唧&#xff09;。 在使用之前&#xff0c;我们还需要在设置中输入自己的…

使用vscode进行C++代码开发(linux平台)

使用vscode进行C代码开发(linux平台一、插件安装二、常用快捷键三、重要配置文件四、实际例子1. 编译并运行一个含有多个文件夹和文件的代码工程2. 编译并运行一个依赖第三方库的代码工程参考资料一、插件安装 执行 ctrl shift x打开插件窗口&#xff0c;然后搜索c插件&…

鸡格线(map操作)

鸡格线 (nowcoder.com) 题目描述 你有一个长为n的数组a&#xff0c;你需要支持以下两种操作: 1、输入l, r, k&#xff0c;对区间[1,r]中所有数字执行a; f(a;)操作k次(式中等号表示赋值操作)&#xff0c;之中f(z)round(10、c)&#xff0c;round为四舍五入函数。 2、输出当前数组…

缓存一致性问题怎么解决

关于Redis的其他的一些面试问题已经写过了&#xff0c;比如常见的缓存穿透、雪崩、击穿、热点的问题&#xff0c;但是还有一个比较麻烦的问题就是如何保证缓存一致性。对于缓存和数据库的操作&#xff0c;主要有以下两种方式。先删缓存&#xff0c;再更新数据库先删除缓存&…

Java 多线程 笔记

文章目录实现方式1. 通过Thread类2. 通过Runnable接口3. 通过Callable接口线程状态线程方法1. 线程停止2. 线程休眠sleep3. 线程礼让yield4. 线程强制执行join5. 线程状态观测getState6. 线程优先级守护线程&#xff08;daemon线程同步 TODO线程死锁Lock锁&#xff08;可重入锁…

Typora+Gitee+PicGo搭建图床

引言 markdown原则上不建议使用base64内嵌图片&#xff0c;因为太麻烦。 如果只是在本机浏览、编辑的话&#xff0c;那引用相对路径或者绝对路径即可&#xff0c;但是考虑到要发布、分享的情况&#xff0c;使用图床是比较好的解决方案。 本教程可以快速得到一个相对稳定的免…

单片机数据、地址、指令、控制总线结构

数据、地址、指令&#xff1a;之所以将这三者放在一同&#xff0c;是因为这三者的实质都是相同的──数字&#xff0c;或者说都是一串‘0’和‘1’组成的序列。换言之&#xff0c;地址、指令也都是数据。 指令&#xff1a;具体可参考文章 由单片机芯片的设计者规则的一种数字…

c程序gcc编译常见报错及解决方法整理

目录一、简介二、常见报错及解决方法1、数组定义错误2、Not enough information to produce a SYMDEFs file3、文件乱码<U0000>4、未定义或未申明报错5、代码中误加入中文三、其他相关链接一、简介 本文主要是整理c程序编译过程的常见报错的解决方法&#xff0c;方便大家…

Leetcode.312 戳气球

题目链接 Leetcode.312 戳气球 题目描述 有 n个气球&#xff0c;编号为0到 n - 1&#xff0c;每个气球上都标有一个数字&#xff0c;这些数字存在数组 nums中。 现在要求你戳破所有的气球。戳破第 i 个气球&#xff0c;你可以获得 nums[i−1]∗nums[i]∗nums[i1]nums[i - 1]…

C++ 类 对象初学者学习笔记

C 类 & 对象 访问数据成员 #include <iostream>using namespace std;class Box {public:double length; // 长度double breadth; // 宽度double height; // 高度// 成员函数声明double get(void);void set( double len, double bre, double hei ); }; // 成员…

两种在CAD中加载在线卫星影像的方法

概述 经常使用CAD的朋友应该会有这样的一个烦恼&#xff0c;就是当加载卫星图到CAD中进行绘图的时候&#xff0c;由于CAD本身的限制和电脑性能等原因&#xff0c;往往不能加载太大的地图图片到CAD内&#xff0c;这里给大家介绍两种在CAD内加载在线卫星影像的方法&#xff0c;希…

用docker部署webstack导航网站-其一

序言 认识的好朋友斥资买了一个NAS&#xff0c;并搭建了webdev和其他一些web应用&#xff0c;用于存放电子书、电影&#xff0c;并用alist搭建了一个网盘。现在他还缺少一个导航页&#xff0c;于是拖我给他做一个导航页。我欣然接受了&#xff0c;他想做一个和TBox导航&#x…

Arduino开发:网页控制ESP8266三色LED灯闪烁

根据板卡原理RGB三色LED对应引脚&#xff1a;int LEDR12、int LEDG14、int LEDB13;设置串口波特率为115200Serial.begin(115200);源代码如下所示&#xff1a;#include <ESP8266WiFi.h> // 提供 Wi-Fi 功能的库#include <ESP8266WebServer.h> // 提供网站服务器功能…

分布式共识算法随笔 —— 从 Quorum 到 Paxos

分布式共识算法随笔 —— 从 Quorum 到 Paxos 概览: 为什么需要共识算法&#xff1f; 昨夜西风凋碧树&#xff0c;独上高楼&#xff0c;望尽天涯路 复制(Replication) 是一种通过将同一份数据在复制在多个服务器上来提高系统可用性和扩展写吞吐的策略, 。常见的复制策略有主从…

Flink CDC 原理

文章目录CDC&#xff0c;Change Data Capture 变更数据捕获 目前CDC有两种实现方式&#xff0c;一种是主动查询、一种是事件接收。 主动查询&#xff1a; 相关开源产品有Sqoop、Kafka JDBC Source等。 用户通常会在数据原表中的某个字段中&#xff0c;保存上次更新的时间戳或…

一篇博客教会你写序列化工具

文章目录什么是序列化&#xff1f;序列化格式JSON序列化精简序列化数据总结源码什么是序列化&#xff1f; 总所周知&#xff0c;在Java语言中&#xff0c;所有的数据都是以对象的形式存在Java堆中。 但是Java对象如果要存储在别的地方&#xff0c;那么单纯的Java对象就无法满…

我靠steam/csgo道具搬运实现财富自由

和莘莘学子一样&#xff0c;本着毕业对未来的憧憬&#xff0c;规划着漫漫人生&#xff0c;可是被残酷的事实打败。 直到一次偶然的同学聚会&#xff0c;谈及了现如今的生活才发现一片新大陆&#xff0c;通过信息差去赚取收益。 记得之前在校期间常常和哥几个通宵干CSGO,直到这…

Elasticsearch7.8.0版本高级查询—— 完全匹配查询文档

目录一、初始化文档数据二、完全匹配查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; { "name":"zhangsan", &quo…

【Nginx】使用Docker完成Nginx负载均衡+动静分离

前提是需要配置Nginx的反向代理&#xff0c;可以我看之前的文章 上篇Nginx配置动态代理的文章&#xff0c;我们在tomcat里写了两个简单html 这次我们依然采取同样的思路来演示负载均衡 一、负载均衡 1.在两个Tomcat容器&#xff08;我这里一个端口8081&#xff0c;一个8082…