当前位置: 首页 > article >正文

Java算法 数据结构基础 并查集 模版 [洛谷-P3367]

目录

题目地址

题目描述

输入输出样例

并查集模版

介绍

1. 路径压缩(Path Compression)

2. 按秩合并(Union by Rank / Size)

代码讲解

操作讲解

时间复杂度分析

应用场景


题目地址

【模板】并查集 - 洛谷

题目描述

输入输出样例

并查集模版

class UnionFind {
    private int[] parent; // 存储每个元素的父节点
    private int[] size;   // 存储每个集合的大小,用于按秩合并

    // 初始化并查集
    public UnionFind(int n) {
        parent = new int[n + 1]; // 因为编号从 1 到 N,所以数组大小是 N+1
        size = new int[n + 1];   // 存储每个集合的大小
        for (int i = 1; i <= n; i++) {
            parent[i] = i;  // 每个元素的父节点初始为自己
            size[i] = 1;    // 每个元素的初始大小为 1
        }
    }

    // 查找元素 x 所在的集合,带路径压缩优化
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩:将父节点直接指向根节点
        }
        return parent[x];  // 返回根节点(集合的代表)
    }

    // 合并两个集合,带按秩合并优化
    public void union(int x, int y) {
        int rootX = find(x);  // 查找 x 的根节点
        int rootY = find(y);  // 查找 y 的根节点

        if (rootX != rootY) {  // 如果根节点不同,说明它们不在同一个集合
            // 按秩合并:较小的集合合并到较大的集合
            if (size[rootX] < size[rootY]) {
                parent[rootX] = rootY;
                size[rootY] += size[rootX];  // 更新根节点的大小
            } else {
                parent[rootY] = rootX;
                size[rootX] += size[rootY];  // 更新根节点的大小
            }
        }
    }

    // 判断 x 和 y 是否在同一个集合中
    public boolean connected(int x, int y) {
        return find(x) == find(y);  // 如果两个元素的根节点相同,说明在同一个集合中
    }
}

介绍

并查集(Union-Find)是一种数据结构,主要用于处理一些不交集的合并及查询问题,特别是在图论中用来解决连通性问题。并查集支持两种基本操作:

  1. 查找(Find):判断某个元素属于哪个集合,返回该集合的代表元素(根)。
  2. 合并(Union):将两个集合合并成一个集合。

并查集通过 路径压缩按秩合并 来优化效率,减少操作的时间复杂度。

1. 路径压缩(Path Compression)

路径压缩是一种优化查找操作的方法。在查找过程中,我们不仅仅返回当前元素的根节点,还将当前节点的父节点直接指向根节点,这样可以加速以后的查找操作。也就是说,当你调用 find(x) 时,你将 x 的祖先节点都直接连接到根节点。

2. 按秩合并(Union by Rank / Size)

按秩合并是一种优化合并操作的方法,旨在确保树的深度(或大小)尽可能小。在合并两个集合时,较小的集合(树)会被合并到较大的集合(树)上。这样,最终形成的树的深度会较小,从而提高查找操作的效率。

代码讲解

java


复制代码
class UnionFind {
    private int[] parent; // 存储每个元素的父节点
    private int[] size;   // 存储每个集合的大小,用于按秩合并

    // 初始化并查集
    public UnionFind(int n) {
        parent = new int[n + 1]; // 因为编号从 1 到 N,所以数组大小是 N+1
        size = new int[n + 1];   // 存储每个集合的大小
        for (int i = 1; i <= n; i++) {
            parent[i] = i;  // 每个元素的父节点初始为自己
            size[i] = 1;    // 每个元素的初始大小为 1
        }
    }

    // 查找元素 x 所在的集合,带路径压缩优化
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩:将父节点直接指向根节点
        }
        return parent[x];  // 返回根节点(集合的代表)
    }

    // 合并两个集合,带按秩合并优化
    public void union(int x, int y) {
        int rootX = find(x);  // 查找 x 的根节点
        int rootY = find(y);  // 查找 y 的根节点

        if (rootX != rootY) {  // 如果根节点不同,说明它们不在同一个集合
            // 按秩合并:较小的集合合并到较大的集合
            if (size[rootX] < size[rootY]) {
                parent[rootX] = rootY;
                size[rootY] += size[rootX];  // 更新根节点的大小
            } else {
                parent[rootY] = rootX;
                size[rootX] += size[rootY];  // 更新根节点的大小
            }
        }
    }

    // 判断 x 和 y 是否在同一个集合中
    public boolean connected(int x, int y) {
        return find(x) == find(y);  // 如果两个元素的根节点相同,说明在同一个集合中
    }
}

操作讲解

  1. 初始化
    • 我们首先创建一个大小为 n + 1parent 数组和 size 数组,parent[i] 存储的是元素 i 的父节点,size[i] 存储的是以 i 为根节点的集合大小。初始化时,每个元素的父节点是它自己,且每个集合的大小为 1。
  1. 查找(Find)操作
    • 查找操作会递归地找元素的父节点,直到找到根节点(即 parent[i] == i)。为了提高查找效率,我们在递归过程中使用路径压缩,将沿途的所有节点的父节点直接指向根节点。
  1. 合并(Union)操作
    • 合并两个集合时,首先找到它们的根节点。如果根节点不同,说明它们属于不同的集合,可以将一个集合合并到另一个集合。为了减少树的深度,我们使用按秩合并的策略,将较小的树合并到较大的树上。
  1. 判断是否连接(Connected)
    • 如果两个元素的根节点相同,说明它们属于同一个集合;如果根节点不同,则说明它们属于不同的集合。

时间复杂度分析

  • 查找操作:由于路径压缩优化,查找操作的时间复杂度接近常数,实际是 O(α(n)),其中 α(n)阿克曼函数的反函数,增长速度非常缓慢,接近常数。
  • 合并操作:合并操作的时间复杂度也是 O(α(n))

因此,并查集的所有操作几乎都可以视为常数时间复杂度 O(α(n))

应用场景

  • 图的连通性问题:例如判断图中的两个节点是否连通。
  • 动态连通性问题:在动态变化的图中,需要不断地进行节点的合并和查询。
  • 集合合并问题:如并购合并、社交网络中的好友关系等。

并查集通过路径压缩和按秩合并的优化,能在大规模数据中高效处理集合合并与查询问题,是非常高效的算法之一。


http://www.kler.cn/a/504754.html

相关文章:

  • vue3 uiapp实现一个数字输入组件, 舒服非数字会默认转成最小数
  • Java中的并发工具类:让多线程编程更轻松
  • 微信小程序集成Vant Weapp移动端开发的框架
  • MySQL 中删除重复数据 SQL 写法
  • 【大数据】机器学习-----线性模型
  • R.swift库的详细用法
  • Masked_Filled随机置列为零
  • 集合帖:区间问题
  • 自建RustDesk服务器
  • BERT的中文问答系统65
  • C语言重点回顾(持续更新中~)
  • 【C#深度学习之路】如何使用C#实现Yolo8/11 Segment 全尺寸模型的训练和推理
  • 实战web 渗透测试教学课程
  • Copilot 和 Windsurf哪个更适合于.netcore开发
  • 获取文章分类详情功能
  • 永久免费日志增量采集工具
  • ubuntu20升级至22后不兼容ssh-rsa加密算法
  • 【C++】揭秘类与对象的内在机制(核心卷之构造函数与析构函数的奥秘)
  • [MRCTF2020]Xor
  • 电机控制01 - 入门篇
  • 设计和优化用于 AR、HUD 和高级显示系统的表面浮雕光栅
  • 指令微调(Instruction Fine-Tuning)
  • LeetCode —— 数组
  • Chapter 3-11. Detecting Congestion in Fibre Channel Fabrics
  • MySQL常用指令
  • C语言 - 可变参数函数 va_list、va_start、va_arg、va_end