数据结构与算法分析之并查集

news2024/12/23 9:01:58

1. 并查集

  • 并查集是一种树型的数据结构,并查集可以高效的进行如下操作:
    • 查询元素p和元素q是否属于同一组
    • 合并元素p和元素q所在的组
      在这里插入图片描述

1.1 并查集结构

  • 并查集是一种树型结构,但这棵树和我们之前学的二叉树、红黑树、B树等都不一样,这种树的要求比较简单:
    • 每个元素都唯一的对应一个结点
    • 每一组数据中的多个元素都在同一棵树中
    • 一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系
    • 元素在树中并没有子父级关系的硬性要求
      在这里插入图片描述

1.2 并查集API设计

在这里插入图片描述

1.3 并查集实现

1.3.1 UF(int N)构造方法实现

  • 初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组
  • 初始化数组eleAndGroup
  • 把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该结点所在的分组,那么初始化的情况下,i索引处存储的值就是i
    在这里插入图片描述

1.3.2 union(int p, int q)合并方法实现

  • 如果p和q已经在同一个分组中,则无需合并
  • 如果p和q不在同一个分组,则只需要将p元素所在的组的所有的元素的组标识符修改为q元素所在组的标识符即可
  • 分组数量-1
    在这里插入图片描述

1.3.3 代码实现

package com.tiger.study.DataStructure.uf;

public class UF {
    // 记录结点元素和改元素所在分组的标识
    private int[] eleAndGroup;

    // 记录并查集中数据的分组个数
    private int count;

    // 初始化并查集
    public UF(int N) {
        // 初始化分组的数量,默认情况下有N个数组
        this.count = N;

        // 初始化eleAndGroup数组
        this.eleAndGroup = new int[N];

        // 初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集每个索引处的值(该元素所在的组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }

    }

    // 获取当前并查集中有多少个分组
    public int count() {
        return count;
    }

    // 元素p所在的分组的标识符
    public int find(int p) {
        return eleAndGroup[p];
    }

    // 判断并查集中元素p和元素q是否是在同一个分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    // 把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        // 判断元素q和p是否在同一分组中,如果在结束
        if (connected(p, q)) {
            return;
        }

        // 找到p所在的标识符
        int pGroup = find(p);

        // 找到q所在的标识符
        int qGroup = find(q);

        // 合并组:让p所在组的所有元素的组的标识符变为q所在分组的标识符
        for (int i = 0; i < eleAndGroup.length; i++) {
            if (eleAndGroup[i] == pGroup) {
                eleAndGroup[i] = qGroup;
            }
        }

        this.count--;

    }

}

1.4 并查集应用举例

  • 如果我们并查集存储的每一个整数表示的是一个大型计算机网络中的计算机,则我们就可以通过connected(int p,int q)来检测,该网络中的某两台计算机之间是否连通?如果连通,则他们之间可以通信,如果不连通,则不能通信,此时我们又可以调用union(int p,int q)使得p和q之间连通,这样两台计算机之间就可以通信了。

1.5 UF_Tree算法优化

  • 为了提升union算法的性能,我们需要重新设计find方法和union方法的实现,此时我们先需要对我们之前数据结构中的eleAndGroup数组的含义进行重新设定
    • 我们仍然让eleAndGroup数组的索引作为某个结点的元素
    • eleAndGroup[i]的值不再是当前结点所在的分组标识,而是该节点的父结点
      在这里插入图片描述

1.5.1 UF_Tree API设计在这里插入图片描述

1.5.2 find(int p)查询方法实现

  • 判断当前元素p的父结点eleAndGroup[p]是不是自己,如果是自己则证明已经是根结点了
  • 如果当前元素p的父结点不是自己,则让p=eleAndGroup[p],继续找父结点的父结点直到找到根结点为止
    在这里插入图片描述

1.5.3 union(int p, int q)合并方法实现

  • 找到p元素所在树的根结点
  • 找到q元素的根结点
  • 如果p和q已经在同一个树中,则无需合并
  • 如果p和q不在同一个分组,则只需要将p元素所在树根结点设置为q元素的根结点即可
  • 分组数量-1
    在这里插入图片描述

1.5.4 代码实现

package com.tiger.study.DataStructure.uf;

public class UF_Tree {
    // 记录结点元素和该结点所在分组的标识
    private int[] eleAndGroup;

    // 记录并查集中的数据分组的个数
    private int count;

    // 初始化并查集
    public UF_Tree(int N) {
        // 初始化分组的数量,默认情况下有N个分组
        this.count = N;

        // 初始化分组的数量,默认情况下,有N个分组
        this.count = N;

        // 初始化eleAndGroup数组
        this.eleAndGroup = new int[N];

        // 初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集每个索引处的值(该元素所在的组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }
    }

    // 获取当前并查集中的数据有多少个分组
    public int count() {
        return count;
    }

    // 判断并查集中元素p和元素q是否是在同一个分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    // 元素p所在的分组的标识符
    public int find(int p) {
        while (true) {
            if (p == eleAndGroup[p]) {
                return p;
            }
            p = eleAndGroup[p];
        }
    }

    // 把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        // 找到p和q所在组对应树的的根结点
        int pRoot = find(p);
        int qRoot = find(q);

        // 如果p和q已经在同一分组,则不需要合并了
        if (pRoot == qRoot) {
            return;
        }

        // 让p所在树的根结点的父结点为q所在树的根结点即可
        eleAndGroup[pRoot] = qRoot;
        this.count--;
    }


}

1.5.5 路径压缩

  • 之前我们这哎union算法中,合并树时将任意的一棵树连接到另外一棵树,这种合并方法是比较暴力的,如果我们把并查集中每一棵树的大小记录下来,然后再每次合并树的时候,把较小的树连接到较大的树上,就可以减小树的深度。只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高find方法的效率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码调整数组中的值
    在这里插入图片描述

1.5.6 UF_Tree_Weighted API设计

在这里插入图片描述

1.5.7 代码实现

package com.tiger.study.DataStructure.uf;

public class UF_Tree_Weighted {
    // 记录结点元素和该结点所在分组的标识
    private int[] eleAndGroup;

    // 记录并查集中的数据分组的个数
    private int count;

    // 用来存储每一个根结点对应的树中保存的结点的个数
    private int[] sz;

    // 初始化并查集
    public UF_Tree_Weighted(int N) {
        // 初始化分组的数量,默认情况下有N个分组
        this.count = N;

        // 初始化分组的数量,默认情况下,有N个分组
        this.count = N;

        // 初始化eleAndGroup数组
        this.eleAndGroup = new int[N];

        // 默认情况下sz中每一个元素的值都是1
        this.sz = new int[N];
        for (int i = 0; i < sz.length; i++) {
            sz[i] = 1;
        }

        // 初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集每个索引处的值(该元素所在的组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }
    }

    // 获取当前并查集中的数据有多少个分组
    public int count() {
        return count;
    }

    // 判断并查集中元素p和元素q是否是在同一个分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    // 元素p所在的分组的标识符
    public int find(int p) {
        while (true) {
            if (p == eleAndGroup[p]) {
                return p;
            }
            p = eleAndGroup[p];
        }
    }

    // 把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        // 找到p和q所在组对应树的的根结点
        int pRoot = find(p);
        int qRoot = find(q);

        // 如果p和q已经在同一分组,则不需要合并了
        if (pRoot == qRoot) {
            return;
        }

        // 判断pRoot对应的树大,还是qRoot对应的树大,最终需要把较小的树合并到较大的树中
        if (sz[pRoot] < sz[qRoot]) {
            eleAndGroup[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            eleAndGroup[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
        this.count--;
    }


}

二. 案例分析

  • 需求:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程"的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
  • 在我们的测试数据文件夹中有一个trffic_project.txt文件,它就是诚征道路统计表,下面是对数据的解释:
    在这里插入图片描述
  • 思路
    • 创建一个并查集UF_Tree_Weighted(20)
    • 分别调用union(0,1),union(6,9),union(3,8),union(5,11),union(2,12),union(6,10),union(4,8),表示已经修建好的道路把对应的城市连接起来;
    • 如果城市全部连接起来,那么并查集中剩余的分组数目为1,所有的城市都在一个树中,所以,只需要获取当前并查集中剩余的数目,减去1,就是还需要修建的道路数目;
  • 代码实现
package com.tiger.study.DataStructure.Test.UF;

import com.tiger.study.DataStructure.uf.UF_Tree_Weighted;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Traffic_Project_Test {
    public static void main(String[] args) throws IOException {
        // 构建一个缓冲读取流BufferReader
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\Study_Code\\src\\main\\java\\com\\tiger\\study\\DataStructure\\Test\\UF\\traffic_project.txt")));

        // 读取第一行数据20
        int totalNumber = Integer.parseInt(br.readLine());

        // 构建并查集对象
        UF_Tree_Weighted uf = new UF_Tree_Weighted(totalNumber);

        // 读取第二行数据7代表已经修好了多少条到了
        int roadNumber = Integer.parseInt(br.readLine());

        // 循环读取7条道路
        for (int i = 0; i < roadNumber; i++) {
            String line = br.readLine();
            String[] str = line.split(" ");
            int p = Integer.parseInt(str[0]);
            int q = Integer.parseInt(str[1]);
            // 调用并查集对象union方法让两个城市相通
            uf.union(p, q);
        }

        // 获取当前并查集分组的数量-1就可以得到还需要修建的道路的数量
        int roads = uf.count() - 1;
        System.out.println("还需要修建" + roads + "条道路才能实现畅通工程。");


    }
}

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

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

相关文章

WindowsServer2016配置故障转移群集图文教程

准备工作 首先准备好两台以上的服务器&#xff0c;并记录好IP和主机名。如下 172.31.210.203 WIN-S8LC9RKL4BB 172.31.215.54 WIN-76A6N72MRTD 之后需要配置到host文件中。 正文 基础配置 首先打开host文件&#xff0c;把所有的IP及主机添加进去。host文件地址如下。 C:\Wi…

10.30-11.3|浙大报考点硕士研究生2023年网上确认系统操作流程

一、登陆《2023年全国硕士研究生招生考试网上确认系统》https://yz.chsi.com.cn/wsqr/stu/index.html。填写考生所在地&#xff0c;点击“确定”。二、点击“开始进行网上确认”。 三、阅读网报公告之后&#xff0c;点击“我已经阅读完毕”。 四、阅读考生诚信考试承诺书&#…

QT5串口编程——编写简单的上位机

下面开始介绍串口类的使用。 首先&#xff0c;QT5是自带QSerialPort这个类的&#xff0c;使用时需要在pro文件里面添加一行&#xff1a; ​然后直接引用头文件就可以了。 ​在QT5中&#xff0c;串口通信是借助一个QSerialPort的对象来实现的&#xff0c;在设置QSerialPort对象…

课堂笔记| 第七章:多态

本节课要点&#xff1a; 继承特性多态虚函数目录 一、多继承 二、继承的前提&#xff1a;正确的分类 三、多态 1. 虚函数 2. 确保覆盖和终止覆盖 3. 虚函数的实现原理 4. 虚析构函数 四、纯虚函数和抽象类 1. 纯虚函数 2. 抽象类 一、多继承 在之前的课程中&a…

SpringCloud Alibaba Sentinel实现熔断与限流

目录 一、简介 1.官网 & 介绍 2.下载地址 3.作用 4.如何使用 ⭐解决服务使用中的各种问题 5.Sentinel与Hystrix的区别 二、安装sentinel控制台 1.sentinel组件由2部分构成 2.安装步骤 ①地址 ②运行命令 ③访问sentinel界面 三、初始化演示工程 1.启动naco…

MyBatis 环境搭建配置全过程【IDEA】

文章目录一、MyBatis 介绍二、MyBatis 环境搭建1.MyBatis 下载2.配置 jdk 版本3.创建 Maven 工程4.IDEA 连接数据库5.项目文件构架6.引入相关依赖7.命令行创建数据库8.数据库配置文件9.核心配置文件三、入门测试程序1.创建表准备数据2.创建 POJO 实体3.创建映射文件4.修改核心配…

如何增加 KVM 虚拟机的磁盘大小

KVM 是一种集成到 Linux 内核中的虚拟化技术。您可以使用virsh、virt-manager和GNOME Boxes等工具创建虚拟机并与 KVM 交互。 磁盘空间不足是最常见的 VM 来宾问题之一。在测试新 VM 时,您可能会故意使用较小的磁盘。随着时间的推移,您会累积文件,直到虚拟磁盘几乎已满。以…

C语言笔记

fabs用来求double类型的绝对值&#xff0c;小数点后保留6位#include<math.h> double fabs(double ) labs用来求长整型long整型的绝对值&#xff0c; long cabs(long n); abs用来求整数的绝对值&#xff0c;labs求long long的绝对值#include<stdlib.h> double ret …

初识C++ (二)

初识C 二 上节课输入输出问题的一些补充一. 缺省参数1.1 半缺省参数1.2 全缺省参数二. 函数重载2.1 重载是什么意思&#xff1f;2.2 如何区分重载函数参数类型不同参数个数不同参数顺序不同附加题1附加题22.3 c支持函数重载的原理预处理编译汇编连接总结要以一种很认真的态度去…

深度优先搜索(dfs)和广度优先搜索(bfs)

目录 一、前言 二、关于dfs和bfs有意思的小故事 三、深搜题例 1、小猫爬山链接 2、基本思路 3、代码 &#xff08;1&#xff09;python代码 四、广搜题例 1、武士风度的牛链接 2、基本思路 3、代码 &#xff08;1&#xff09;C代码 &#xff08;3&#xff09;pyth…

现在的编程语言越来越多,为什么 C 和 C++ 还没有被现在的时代淘汰呢?

C/C会不会被时代淘汰&#xff1f;这个问题跳过了一步&#xff0c;关键是这个问题&#xff1a; C/C有哪些其它语言难以代替的特殊之处&#xff1f; 1、对实现细节的控制粒度 一般我们常说&#xff1a;C/C具有较高的执行效率。其实这句话不是特别准确&#xff0c;有时候它们并…

npm报错整理

npm报错整理一、代理1. 因为使用公司的镜像源导致的403 forbidden总结一、代理 1. 因为使用公司的镜像源导致的403 forbidden 在更新脚手架的时候&#xff0c;遇到了403的报错&#xff1a; 遇到问题不要怕&#xff0c;我们根据错误去解决就好。 &#xff08;1&#xff09;首…

【黄啊码】MySQL入门—13、悲观锁、乐观锁怎么用?什么是行锁、页锁和表锁?死锁了咋办?

大家好&#xff01;我是黄啊码&#xff0c;MySQL的入门篇已经讲到第12个课程了&#xff0c;今天我们继续讲讲大白篇系列——数据库锁 目录 从数据库管理的角度对锁进行划分 共享锁也叫读锁或 S 锁 排它锁也叫独占锁、写锁或 X 锁。 意向锁&#xff08;Intent Lock&#xf…

C++库——windows下使用Qt5.15.2+mingw64+msys2编译c++数学库GSL

文章目录准备配置msys2编译GSL准备 下载gsl库的源代码。大家可以到GSL的官网下载gsl的源代码。目前版本为2.7&#xff0c;下载完成后解压缩。 下载msys2。msys2是一套在windows上运行的用于构建库和程序的工具库&#xff0c;下载地址可以使用清华源的下载地址。下载完成后&…

【论文解读】伪装物体检测 Camouflaged Object Detection

文章目录伪装物体检测 Camouflaged Object DetectionSINet v1RF模块&#xff1a;PDC模块&#xff1a;SINet v2特征提取Texture Enhanced Module 纹理增强模块Neighbor Connection Decoder 邻居连接解码器Group-Reversal Attention 组反转注意力总结伪装物体检测 Camouflaged Ob…

计算机毕业设计之java+javaweb的烯烃厂压力管道管理平台

项目介绍 系统权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1b;管理员使用本系统涉到的功能主要有&#xff1a;主页、个人中心、通知公告管理、用户管理、管道信息管理、单位信息管理、管道统计信息管理等功能。 (b) 用户登录进入系统可以对主页、个人中心、通…

2022高频经典前端面试题(html+css+js上篇,含答案)

博主经历过多轮面试&#xff0c;因此想将自己的面试经验以及答题技巧&#xff0c;分享给即将面试找前端工作的同学。 2022高频经典前端面试题分为上中下三篇&#xff0c;分别会有html,css,js,es6,vue,ts,nodejs,以及hr面和反问面试官几个维度去进行&#xff0c;完整的还原面试场…

在 Linux 中使用 tcp 转储命令来分析网络

前言 Tcpdump是用于分析网络和查找相关网络问题的出色工具。它会在数据包经过时捕获数据包&#xff0c;并向您显示网络上正在发生的事情和传入情况。该命令的输出显示在 STDOUT 上&#xff0c;也可以存储在文件中。 感谢开发人员&#xff0c;他们将Tcpdump保留为开源项目。它…

LinkedIn最好工具-领英精灵有哪些批量加好友方法?

领英工具-领英精灵有哪些批量加好友方法 使用领英的人都会使用领英精灵&#xff0c;因为领英精灵是目前本土做得最好的领英工具&#xff0c;具有很多强大的功能。特别是拓展人脉方面&#xff0c;提供了很多批量加好友的方法。刚使用的新手可能不知道如何操作&#xff0c;下面就…

施耐德电气“创新开放日”走进中国软件研发中心 以软件与创新驱动产业“双转型”

来源 | 施耐德电气 2022年10月27日&#xff0c;施耐德电气在位于北京亦庄的中国软件研发中心举办“创新开放日”&#xff0c;充分展示其在中国深化研发的战略布局。当天&#xff0c;施耐德电气展示了该中心成立一周年以来的创新研发成果&#xff0c;并与合作伙伴共话软件发展趋…