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

并查集 (Union-Find) :从基础到优化

并查集 (Union-Find)

并查集是一种树形数据结构,主要用于处理不相交集合(Disjoint Set)的合并和查询问题。它特别适用于解决有关连通性的问题,比如在图论中判断两点是否在同一个连通分量中。并查集可以高效地支持以下两种操作:

  1. 查找(Find):确定某个元素属于哪个集合。
  2. 合并(Union):将两个不同的集合合并为一个集合。

属于集合关系

合并

基础数据结构

每个集合最初由一个元素构成,每个元素都是自己集合的代表。可以用一个数组 parent 来保存每个元素的父节点。而合并操作则在不考虑树的深度的情况下直接将一个集合的根节点指向另一个集合的根节点。

初始化

用一个数组 parent 来表示集合,其中 parent[i] 表示节点 i 的父节点。初始化时,每个元素的父节点都是它自己,即每个元素都是自己集合的代表。
初始化

int[] parent = new int[n];
for (int i = 0; i < n; i++) {
    parent[i] = i;  // 每个元素的父节点指向自己
}

查找(Find)

查找操作用于找到元素所在集合的代表节点(根节点)。通过沿着父节点不断往上查找,直到找到一个节点,它的父节点是它自己,也就找到了根节点。

int find(int x) {
    while (parent[x] != x) {
        x = parent[x];  // 沿着父节点向上查找根节点
    }
    return x;
}

合并(Union)

合并操作将两个集合合并。最简单的方法是找到两个集合的根节点,然后将其中一个根节点指向另一个根节点,不考虑树的深度。

void union(int x, int y) {
    int rootX = find(x);
    int rootY = find(y);
    if (rootX != rootY) {
        parent[rootX] = rootY;  // 将一个根节点指向另一个根节点
    }
}

在这个最基础的实现中,合并操作是随意进行的,不考虑树的结构或平衡性。因此,树可能会变得非常深,甚至变成一条链表,这会导致查找操作的效率降低。

算法优化

路径压缩

在查找操作时,通过让节点直接指向根节点,降低树的深度。
路径压缩

int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]); // 路径压缩
    }
    return parent[x];
}

按秩合并

可以理解为一个树的深度(高度)。在合并时,可以将秩较小的树合并到秩较大的树上,使树的深度尽量小。因此加入一个 rank 数组记录节点所在树中的高度,用它来控制合并时的平衡性,初始时每个集合的秩都为 1。
按秩合并

控制逻辑如下:

  • 如果 rank[rootX] > rank[rootY],则令 parent[rootY] = rootX
  • 如果 rank[rootX] < rank[rootY],则令 parent[rootX] = rootY
  • 如果 rank[rootX] == rank[rootY],则可任意指定根节点,同时将秩的值加 1。
void union(int x, int y) {
    int rootX = find(x);
    int rootY = find(y);
    if (rootX != rootY) {
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}

代码实现

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    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);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX]++;
            }
        }
    }
}

应用

LeetCode: 1971. 寻找图中是否存在路径


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

相关文章:

  • YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11 推理的 C++ 和 Python 实现
  • 无人机检测车辆——多目标检测
  • 双子数(枚举素数)
  • 五、函数封装及调用、参数及返回值、作用域、匿名函数、立即执行函数
  • 《基于Oracle的SQL优化》读书笔记
  • 恒流数显驱动数显LED驱动芯片VK16D32
  • C++学习笔记(35)
  • 数组的练习
  • 域 缺省参数 函数重载 引用
  • 828华为云征文|部署基于 LLM 的私有知识库系统 AnythingLLM
  • Magnific推V2图像生成服务 可直出4K图像
  • 发掘3D文件格式的无限潜力:打造沉浸式虚拟世界
  • 数据结构:树(并查集)
  • LeetCode[中等] 138. 随机链表的复制
  • 9.28学习
  • 人工智能的基本概念与发展历程
  • 【第十四章:Sentosa_DSML社区版-机器学习之时间序列】
  • 从碎片到整合:EasyCVR平台如何重塑城市感知系统的视频数据生态
  • 【matlab画多纵坐标图像】
  • io流(学习笔记04)io流的概述
  • 看Threejs好玩示例,学习创新与技术(Noise)
  • 饿了么 表单 回填后 无法更新 问题
  • Rider快捷键笔记
  • 计算机视觉方面的一些模块
  • Linux下如何实现不用加路径调用启动脚本
  • IP地址与智能家居能够碰撞出什么样的火花呢?