【无标题】

并查集(Disjoint Set Union)详解:原理、优化与应用

并查集(也称为不相交集合数据结构 ,英文 Disjoint Set Union 或 Union-Find)是一种非常高效的数据结构,主要用于处理动态连通性问题。它支持两种核心操作:

  • 查询(Find):判断两个元素是否属于同一个集合(即是否连通)。
  • 合并(Union):将两个集合合并成一个。

并查集在算法竞赛(如 OI、ACM)和实际工程中应用广泛,例如最小生成树(Kruskal 算法)、图的连通分量统计、网格连通问题等。其最大特点是操作几乎达到 O(1) 时间复杂度(摊销后)。

本文将从基本原理入手,逐步介绍实现、优化技巧,并附上完整代码示例。

1. 并查集的基本原理

并查集用一个森林 (多棵树)来表示多个不相交的集合。每棵树代表一个集合,树的根节点作为该集合的代表元。

  • 用一个数组 parent[] 表示每个节点的父节点。
  • 初始时,每个元素都是独立的集合:parent[i] = i
  • 查找根节点(Find 操作):从某个节点向上追溯,直到找到父节点为自己本身的节点(根)。
  • 合并(Union 操作):找到两个元素的根节点,如果不同,则将一棵树的根指向另一棵树的根。

简单实现(无优化)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int parent[N];

void init(int n) {
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
    }
}

int find(int x) {
    if (parent[x] == x) return x;
    return find(parent[x]);  // 递归查找根
}

void union_sets(int x, int y) {
    int rx = find(x);
    int ry = find(y);
    if (rx != ry) {
        parent[rx] = ry;  // 将 rx 的根指向 ry 的根
    }
}

这种实现最坏情况下树会退化成一条链(高度为 n),每次 Find 操作可能需要 O(n) 时间。

2. 优化一:路径压缩(Path Compression)

路径压缩的核心思想是:在查找根节点的过程中,将路径上的所有节点直接指向根节点,从而压扁树的高度。

修改 Find 函数:

cpp 复制代码
int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]);  // 递归压缩路径
    }
    return parent[x];
}

效果示意:

  • 未压缩:1 → 2 → 3 → 4(根)
  • 压缩后:1、2、3 都直接指向 4

路径压缩后,树的高度会大幅降低,后续查询极快。

3. 优化二:按秩合并(Union by Rank)

单纯路径压缩虽然优秀,但合并时如果总是随意连接,可能导致树偏斜。按秩合并通过维护一个 rank[] 数组记录每棵树的"秩"(近似高度)来优化。

  • 初始时,所有秩为 1(或 0)。
  • 合并时,总是将秩小的树挂到秩大的树上
  • 如果秩相等,任选一个挂接,并将胜出方的秩 +1。
cpp 复制代码
int rank[N];

void init(int n) {
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
        rank[i] = 1;  // 初始秩为 1
    }
}

void union_sets(int x, int y) {
    int rx = find(x);
    int ry = find(y);
    if (rx == ry) return;
    // 将秩小的合并到秩大的分支上
    if (rank[rx] < rank[ry]) {
        parent[rx] = ry;
    } else {
        parent[ry] = rx;
        if (rank[rx] == rank[ry]) {
            rank[rx]++;
        }
    }
}

路径压缩 + 按秩合并 是标准优化组合,摊销时间复杂度为 O(α(n)),其中 α(n) 是反阿克曼函数,增长极慢,实际可视为常数。

4. 完整优化版代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int parent[N];
int rank[N];

void init(int n) {
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
        rank[i] = 1;
    }
}

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

void union_sets(int x, int y) {
    int rx = find(x);
    int ry = find(y);
    if (rx == ry) return;
    
    if (rank[rx] < rank[ry]) {
        swap(rx, ry);  // 保证 rx 的秩更大
    }
    parent[ry] = rx;
    if (rank[rx] == rank[ry]) {
        rank[rx]++;
    }
}

int main() {
    // 示例使用
    init(10);
    union_sets(1, 2);
    union_sets(3, 4);
    union_sets(2, 4);
    cout << (find(1) == find(4) ? "连通" : "不连通") << endl;  // 输出:连通
    return 0;
}

5. 常见应用场景

  1. 图的连通分量统计:遍历所有边进行 Union,最后统计根节点数量。
  2. Kruskal 最小生成树:按边权排序,Union 时判断是否形成环。
  3. 网格/迷宫连通问题:如 LeetCode 的"朋友圈"、"被围绕的区域"等。
  4. 在线判断连通性:动态添加边,实时查询是否连通。

6. 扩展:按大小合并(Union by Size)

另一种优化是维护每个集合的大小 size[],合并时将小集合挂到大集合上。效果与按秩合并类似,有时更优(尤其在需要统计集合大小时)。

cpp 复制代码
int size[N];

void init(int n) {
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
        size[i] = 1;
    }
}

void union_sets(int x, int y) {
    int rx = find(x), ry = find(y);
    if (rx == ry) return;
    if (size[rx] < size[ry]) swap(rx, ry);
    parent[ry] = rx;
    size[rx] += size[ry];
}

总结

并查集是算法竞赛中的"神器"之一,实现简单却效率极高。记住两大优化:

  • 路径压缩:让 Find 更快。
  • 按秩/大小合并:防止树退化。

掌握了并查集,很多图论和连通性问题都会迎刃而解。建议多刷相关题目(如 HDU 1232 畅通工程、POJ 2236 等)来加深理解。

相关推荐
生成论实验室12 分钟前
《事件关系阴阳博弈动力学:识势应势之道》第二篇:阴阳博弈——认知的动力学基础
数据结构·人工智能·科技·神经网络·算法
li16709027023 分钟前
第二十七章:智能指针
c语言·数据结构·c++·visual studio
We་ct40 分钟前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
风筝在晴天搁浅41 分钟前
字节高频题 小于n的最大数
算法
LabVIEW开发43 分钟前
LabVIEW水力机组空蚀在线监测
算法·labview·labview知识·labview功能·labview程序
AI科技星1 小时前
科幻艺术书本封面:《全域数学》第一部·数术本源 第三卷 代数原本(P95-141)完整五级目录【乖乖数学】
算法·机器学习·数学建模·数据挖掘·量子计算
skywalk81631 小时前
在考虑双轨制,即在中文语法的基础上,加上数学公式的支持,这样像很多计算将更加简单方便,就像现在的小学数学课本里面一样,比如:定x=2*x + 1
开发语言
风筝在晴天搁浅1 小时前
LeetCode 92.反转链表Ⅱ
算法·leetcode·链表
小书房1 小时前
Kotlin的by
android·开发语言·kotlin·委托·by
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【贪心与二分判定】:数列分段 Section II
c++·算法·贪心·csp·信奥赛·二分判定·数列分段 section ii