深入理解数据结构 —— 并查集

news2025/1/16 14:01:02

什么是并查集

并查集是一种数据结构,主要能够高效地实现以下两个功能

给出图中任意两点a,b

  • union(a,b) :将a,b所在的集合合并起来
  • isConnected(a,b) :问这a,b两点能否通过任意路径连接起来

同时也能支持:

  • size() :返回并查集中有多少个集合

为什么并查集快

我们也可以先求出两个节点的路径,只要有路径,那么就是连接的,否则就是不连接的

但回答两个节点是否连接,比回答两个节点的路径是什么,需要的信息更少

如果只想知道两个节点是否连接,但却耗费了更多的资源去求解两个节点的路径是什么,而这部分我们并不关心

这就是并查集快的原因:只计算和保存需要的信息

快速查找

假设图中有N个节点,每个节点的编号为0~N-1

则每个节点属于哪个集合,可以用数组int[] parent表示:

在这里插入图片描述

上图中,编号为0,1,2的节点属于编号为0的集合,编号为3,4的节点属于编号为1的集合

isConnected

这样判断a,b是否属于同一个集合,即是否已连接,只用判断parent[a] == parent[b]即可

union

但如果想好将两个集合合并,就比较麻烦

例如,需要将编号为2,3节点所在的集合合并,就需要:

  1. 找到节点2,3所属集合的编号,分别为0,1,集合编号不同,需要合并
  2. 遍历parent数组,要么将所有编号为0的节点的集合编号改为1,要么将1改为0

可以发现这种方式实现并查集,查找很快,但合并很慢,因为需要遍历所有的节点

快速合并

第二种实现方式,将并查集中每个节点相连接,形成的结构

这棵树由孩子指向父亲,如果每棵树代表一个集合,用树的根节点来标识一个集合

这样当判断两个节点是否属于同一集合时,需要从当前节点往上,一直找到根节点,如果根节点相同,说明属于同一个集合
在这里插入图片描述

如上图所示,节点2指向节点3,节点3指向自己,因此节点3是该集合的根节点

还是用parent数组表示所有的集合,对应到上图的示例为:parent[2] = 3,parent[3] = 3

isConnected

判断两个节点是否属于同一集合的操作为:

  • 从每个节点开始,不断往上找,直到找到根节点为止
  • 如果根节点相同,则属于同一集合
public boolean isConnected(int a,int b) {
    return find(a) == find(b);
}

public int find(int v) {
    int cur = v;
    while (cur != p[cur]) {
        cur = p[cur];
    }
    
    return cur;
}

union

合并两个集合:

  • 找到节点a,b所属集合的根节点parentA,parentB
  • 将parentB挂到parentB下面即可,parent[parentA] = parentB

例如:下图将根节点为5的集合,合并到根节点为3的集合中,合并后,根节点为5的结合中的所有节点的根节点变为3

在这里插入图片描述

代码为:

public void union(int a,int b){
    int pa = find(a);
    int pb = find(b);
    if (pa != pb) {
        p[pa] = pb;
    }
}

这种实现方式合并和查找的时间复杂度为O(h),h为树的高度

合并时不用像上一种方法一样,遍历所有的节点,开销较低,但是查找需要遍历当前节点所在的树

因此优化方向为,降低树的高度

基于size的优化

要降低树的高度,可以考虑在union时,是将parentA挂到parentB的下面,还是将parentB挂到parentA的下面:

一种简单的方式处理方式为:将集合元素个数小的根节点,挂到集合元素个数大的根节点下面

因此需要用一个size数组维护以每个节点为根节点的集合的元素个数

新的union代码如下:

public void union(int a,int b){
    int parentA = find(a);
    int parentB = find(b);
    if (parentA != parentB) {
        if (size[parentA] <= size[parentB]) {
            p[parentA] = parentB;
            size[parentB] += size[parentA];
        } else {
            p[parentB] = parentA;
            size[parentA] += size[parentB];
        }
    }
}

路径压缩

要降低树的高度,也可以在find操作时进行

每棵树最理想的情况为,根节点在第一层,其他所有的节点都在第二层,这样后续在这棵树上执行find的时间复杂度为O(1):

在这里插入图片描述

在find操作寻找根节点的过程中,可以顺便将树的高度降低,将树变为上图中最理想的情况

怎么做呢?

  1. 在不断往上找根节点的过程中,将沿途的所有节点记录到一个栈中
  2. 找到根节点后,将栈中所有节点的parent修改为根节点

在这里插入图片描述

代码如下:

public int find(int v) {
    Stack<Integer> stack = new Stack<>();
    stack.push(v);
    while (v != p[v]) {
        v = p[v];
        // 将这条路径上的节点都记录一个栈中
        stack.push(v);
    }
    // 此时v为头结点

    // 路径压缩,将这一条路径上所有节点的父都改为cur
    while (!stack.isEmpty()) {
       p[stack.pop()] = v;
    }
    return v;
}

完整代码

class UF {
    int[] p;
    int[] size;

    int setSize;
    public UF(int cap){
        p = new int[cap];
        for (int i = 0;i<cap;i++){
            p[i] = i;
        }
        setSize = cap;
    }

    public int getSetSize() {
        return setSize;
    }

    public int find(int v) {
        Stack<Integer> stack = new Stack<>();
        stack.push(v);
        while (v != p[v]) {
            v = p[v];
            // 将这条路径上的节点都记录一个栈中
            stack.push(v);
        }
        // 此时v为头结点

        // 路径压缩,将这一条路径上所有节点的父都改为cur
        while (!stack.isEmpty()) {
           p[stack.pop()] = v;
        }
        return v;
    }

    public void union(int a,int b){
        int parentA = find(a);
        int parentB = find(b);
        if (parentA != parentB) {
            if (size[parentA] <= size[parentB]) {
                p[parentA] = parentB;
                size[parentB] += size[parentA];
            } else {
                p[parentB] = parentA;
                size[parentA] += size[parentB];
            }
        }
        setSize--;
    }
    
    public boolean isConnected(int a,int b) {
        return find(a) == find(b);
    }
}

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

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

相关文章

防治新冠病毒感染,儿童如何居家备药?

随着新冠疫情防控的优化&#xff0c;如何为家里的孩子准备药物&#xff1f;这是家长们关心的问题。下面由小熊药师一一解惑。儿童新冠病毒感染主要表现为发热.流涕.咳嗽可能伴有呕吐&#xff0c;可能伴有呕吐.腹泻和其他胃肠道反应。症状一般持续2~5天&#xff0c;其中发热一般…

Map和Set的介绍

目录 1、Map 和 Set 的概念 2、模型 3、Map 的学习 3.1 关于 Map.Entry 3.2 Map 的常用方法 4、Set 的常用方法 5、 Map 和 Set 的注意点 1、Map 和 Set 的概念 Java 提供了 Map 和 Set 的接口&#xff0c;是一种专门用来进行搜索的容器或数据结构&#xff0c;而他搜索…

1.3、操作系统的发展和分类

整体框架 1、手工操作阶段 1.1、主要缺点 用户独占全机、人机速度矛盾导致资源利用率极低 一个用户把自己的程序放入纸带机&#xff0c;等待计算机执行完后&#xff0c;用户再取走自己的数据。 这时候&#xff0c;下一个用户才可以继续接着使用该计算机系统 在一个时间段内只…

1.4、操作系统的运行机制和体系结构

整体框架 1、运行机制 1.1、什么是指令 简单来说&#xff0c;“指令” 就是处理器&#xff08;CPU&#xff09;能识别、执行的最基本命令 比如&#xff1a;加法指令就是让 CPU 进行加法运算 1.2、特权指令&非特权指令 新的问题: 有的指令 “人畜无害” 。 比如&#xf…

【NI Multisim 14.0操作实例——最小系统电路】

目录 序言 &#x1f95d;1.设置工作环境 &#x1f95d; 2.设置原理图图纸 &#x1f95d; 3.设置图纸的标题栏 &#x1f95d; 4.增加元件 &#x1f95d; 5.放置电阻 &#x1f95d; 6.放置无极性电容 &#x1f95d; 7. 放置可变电容 &#x1f95d; 8. 放置电感 &#x…

概论_第3章_两个随机变量的函数的分布__卷积公式

前面&#xff0c; 我详细介绍了 一个随机变量函数的概率分布 &#xff0c;本文开始介绍 两个随机变量的函数。注意&#xff0c; 不能写成 两个随机变量函数&#xff0c; 那就会误认为 两个函数&#xff0c;本文主要介绍两个连续型随机变量的函数&#xff0c; 至于离散型&#x…

网络通信原理——数据传输、OSI模型与TCP/IP、3种数据交换技术

数据传输过程 参考&#xff1a;https://blog.csdn.net/qq_37954088/article/details/80355000、https://baike.baidu.com/item/ARP/609343?fraladdin 基本概念 网络通信的协议体系模型&#xff1a;理想模型OSI(Open System Interconnection)&#xff1b;常用模型TCP/IP OS…

C/C++ 字符指针指向字符串的几种方法

字符指针指向字符串的方法 由于字符串在内存中连续存储的特点&#xff0c;可以使用指针进行操作&#xff0c;并且指针必须是字符型的。通常将指针指向字符串的首地址&#xff0c;利用指针的后移可以指向后续字符。 字符指针指向字符串一般有三种的方法&#xff1a; &#xff0…

【MySQL进阶】多版本并发控制——MVCC

【MySQL进阶】多版本并发控制——MVCC 文章目录【MySQL进阶】多版本并发控制——MVCC一&#xff1a;MVCC 原理1&#xff1a;版本链2&#xff1a;ReadView 简介3&#xff1a;访问记录的规则4&#xff1a;READ COMMITTED —— 每次读取数据前都生成一个 ReadView5&#xff1a;REP…

vue入门到精通(四)

三、vue3组合式API 1、组合式API 1.1 什么是组合式API 组合式 API (Composition API) 是一系列 API 的集合&#xff0c;使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语&#xff0c;涵盖了以下方面的 API&#xff1a; 响应式 API&#xff1a;例…

【安卓学习笔记】Activity的生命周期和加载模式

Activity的生命周期 Activity是安卓应用的重要组成单元之一&#xff0c;其对于安卓的作用类似于Servlet对于Web应用的作用。 整个Activity生命周期的图解如下 具体的生命周期可以总结成如下几个步骤 onCreate()&#xff1a;Activity启动后第一个被调用的函数&#xff0c;常用…

1819. 序列中不同最大公约数的数目

插&#xff1a; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家一起学习鸭~~~ 题目&#xff1a; 给你一个由正整数组成的数组 nums …

四信5G工业路由器全面支持中国移动研究院5G专网质量探针,满足5G专网高质保障需求

面向工业4.0时代&#xff0c;5G行业应用也在不断拓展&#xff0c;大量的5G专网兴起&#xff0c;“一业带百业”效果显著&#xff0c;截至2022年9月&#xff0c;我国5G行业虚拟专网数量已超过1万张&#xff0c;5G已在全国200余家智慧矿山、1700余家智慧工厂、250余个智慧电网项目…

SOFA Weekly|铜锁探「密」、本周贡献 issue 精选

SOFA WEEKLY | 每周精选 筛选每周精华问答&#xff0c;同步开源进展欢迎留言互动&#xff5e;SOFAStack&#xff08;Scalable Open Financial Architecture Stack&#xff09;是蚂蚁集团自主研发的金融级云原生架构&#xff0c;包含了构建金融级云原生架构所需的各个组件&#…

Shiro【授权、整合Spirng、Shiro过滤器】

前言 本文主要讲解的知识点有以下&#xff1a; Shiro授权的方式简单介绍与Spring整合初始Shiro过滤器 一、Shiro授权 上一篇我们已经讲解了Shiro的认证相关的知识了&#xff0c;现在我们来弄Shiro的授权 Shiro授权的流程和认证的流程其实是差不多的&#xff1a; 1.1Shiro支…

React相关扩展二(Fragment、Content、useContext、组件优化、render props、错误边界)(十)

系列文章目录 第一章&#xff1a;React基础知识&#xff08;React基本使用、JSX语法、React模块化与组件化&#xff09;&#xff08;一&#xff09; 第二章&#xff1a;React基础知识&#xff08;组件实例三大核心属性state、props、refs&#xff09;&#xff08;二&#xff0…

本周推荐 | AB实验低响应情景解决实践

推荐语&#xff1a;本文针对AB实验低响应情景下的增量效果不显著问题&#xff0c;提出通过倾向得分匹配方案来衡量策略增量效果的方法&#xff0c;并将相关方案融入一休平台科学评估体系中。文章理论与实践相结合&#xff0c;深入浅出&#xff0c;强烈推荐。——大淘宝技术数据…

9个非常有趣的HTML5 Canvas动画特效合集

HTML5技术正在不断的发展和更新&#xff0c;越来越多的开发者也正在加入HTML5阵营&#xff0c;甚至在移动开发上HTML5的地位也是越来越重要了。HTML5中的大部分动画都是通过Canvas实现&#xff0c;因为Canvas就像一块画布&#xff0c;我们可以通过调用脚本在Canvas上绘制任意形…

计算机视觉OpenCv学习系列:第一部分、绪论

第一部分、绪论第一节、计算机视觉发展历程1.计算机视觉发展历史2.计算机视觉的主要任务3.计算机视觉的应用场景第二节、计算机视觉框架1.早期计算机视觉框架概述2.当前主流的框架与路线3.计算机视觉框架的未来趋势第三节、OpenCV框架1.OpenCV的发展历史2.OpenCV模块架构3.Open…

深入理解Disruptor

Disruptor通过缓存行填充&#xff0c;利用CPU高速缓存&#xff0c;只是Disruptor“快”的一个因素&#xff0c;快的另一因素是“无锁”&#xff0c;尽可能发挥CPU本身的高速处理性能。 1 缓慢的锁 Disruptor作为一个高性能的生产者-消费者队列系统&#xff0c;核心就是通过Ri…