跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

深入理解并查集:原理、优化技巧与 LeetCode 实战

并查集是一种管理不相交集合的数据结构,核心在于高效的查找与合并操作。通过路径压缩和按秩合并优化,其时间复杂度接近常数。详细解析并查集的实现原理,并结合 LeetCode 水位上升的泳池中游泳与省份数量两道经典题目,展示如何利用该结构解决动态连通性问题。

不知所云发布于 2026/3/16更新于 2026/10/2292 浏览
深入理解并查集:原理、优化技巧与 LeetCode 实战

一、认识并查集

1. 并查集的定义

并查集(Disjoint Set)是一种用于管理不相交集合的数据结构。它主要用于处理需要动态维护集合合并和查询操作的问题。其核心功能高效支持两个基本操作:

Find(查找):确定某个元素属于哪一个集合。 Union(合并):将两个元素所在的集合合并为一个集合。

2. 基本概念

2.1. 集合的表示

并查集通常通过一个数组(parent 数组)来表示每个元素的'父节点',从而形成森林结构。每个集合用一棵树表示,树的根节点即为该集合的代表元素。

例如,假设有 5 个元素(0, 1, 2, 3, 4),初始时每个元素都是一个独立的集合:

0 1 2 3 4

对应的 parent 数组为 [0, 1, 2, 3, 4],每个元素的父节点指向自己。

2.2. 合并操作

当需要将两个元素所在的集合合并时,可以将其中一个集合的根节点指向另一个集合的根节点。例如,将元素 1 和元素 0 所在的集合合并:

此时 parent 数组变为 [1, 1, 2, 3, 4],元素 1 和 0 属于同一个集合,根节点是 1。

2.3. 查询操作

查询某个元素属于哪个集合时,只需找到该元素所在树的根节点。例如,查询 find(0) 会沿着父节点链向上直到根节点。

3. 基本操作实现

3.1. 初始化

初始化时,每个元素的父节点指向自己,秩(rank)初始化为 0。

public 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] = 0;
        }
    }
}
3.2. 查找(带路径压缩)

查找操作的目的是找到根节点。为了提高效率,使用路径压缩技术,将查找路径上的所有节点直接指向根节点。

public int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]); // 路径压缩
    }
    return parent[x];
}
3.3. 合并(带按秩合并)

合并时将两个集合的根节点连接。为了避免树变得过高,通常使用按秩合并策略,将秩较小的树合并到秩较大的树上。

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] += 1;
        }
    }
}

4. 优化技巧详解

4.1. 路径压缩

在 find 操作中,递归返回时将沿途节点的父节点直接设为根节点。这大大减少了后续查找的深度,使得单次操作的时间复杂度接近常数。

4.2. 按秩合并

维护每个树的高度(秩)。合并时总是将矮树挂到高树下,保证树的高度增长缓慢。结合路径压缩后,时间复杂度可降至 O(α(n)),其中 α 是阿克曼函数的反函数。

5. 完整代码示例

以下是一个完整的 Java 实现,包含判断连通性的辅助方法:

public 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] = 0;
        }
    }

    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] += 1;
            }
        }
    }

    public boolean isConnected(int x, int y) {
        return find(x) == find(y);
    }

    public static void main(String[] args) {
        int n = 10;
        UnionFind uf = new UnionFind(n);
        uf.union(1, 2);
        uf.union(2, 5);
        System.out.println("Is 1 and 5 connected? " + uf.isConnected(1, 5)); // true
    }
}

6. 应用场景

  • 图的连通性:判断图中是否存在环,或两点是否连通(如 Kruskal 算法求最小生成树)。
  • 社交网络分析:判断用户是否属于同一圈子。
  • 动态连通性问题:频繁更新连通状态的场景。

7. 时间复杂度

  • 初始化:O(n)
  • 查找/合并:均摊 O(α(n)),几乎为常数时间。

二、例题实战

例题一:水位上升的泳池中游泳

题目描述: 在一个 n x n 的整数矩阵 grid 中,grid[i][j] 表示位置 (i, j) 的平台高度。时间为 t 时,水位为 t。你可以从平台游向四周相邻平台,前提是水位必须同时淹没这两个平台。求从左上角 (0, 0) 到达右下角 (n-1, n-1) 所需的最少时间。

示例图

解题思路: 随着时间推移,水位上升,原本不连通的平台可能变得连通。我们可以利用并查集动态维护平台的连通性。

  1. 初始化:将矩阵中的每个平台视为独立集合。
  2. 排序:提取所有平台高度并排序,按高度从小到大处理。
  3. 动态合并:遍历高度,对于当前高度的平台,检查其四个方向。若相邻平台高度小于等于当前水位,则合并。
  4. 判断连通:每次合并后检查起点和终点是否连通,若连通则当前高度即为答案。

代码实现:

private int N;
public static final int[][] DIRECTIONS = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

public int swimInWater(int[][] grid) {
    this.N = grid.length;
    int len = N * N;
    int[] index = new int[len];
    
    // 建立高度到坐标的映射
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            index[grid[i][j]] = getIndex(i, j);
        }
    }

    UnionFind uf = new UnionFind(len);
    
    // 按高度顺序处理
    for (int i = 0; i < len; i++) {
        int x = index[i] / N;
        int y = index[i] % N;
        
        for (int[] d : DIRECTIONS) {
            int newX = x + d[0];
            int newY = y + d[1];
            if (inArea(newX, newY) && grid[newX][newY] <= i) {
                uf.union(index[i], getIndex(newX, newY));
            }
        }
        
        if (uf.isConnected(0, len - 1)) {
            return i;
        }
    }
    return -1;
}

private int getIndex(int x, int y) {
    return x * N + y;
}

private boolean inArea(int x, int y) {
    return x >= 0 && x < N && y >= 0 && y < N;
}

class UnionFind {
    private int[] parent;
    public UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    public int root(int x) {
        while (x != parent[x]) {
            parent[x] = parent[parent[x]]; // 路径压缩
            x = parent[x];
        }
        return x;
    }
    public boolean isConnected(int x, int y) {
        return root(x) == root(y);
    }
    public void union(int p, int q) {
        if (isConnected(p, q)) return;
        parent[root(p)] = root(q);
    }
}

例题二:省份数量

题目描述: 给定 n x n 的矩阵 isConnected,isConnected[i][j] = 1 表示城市 i 和 j 直接相连。返回省份的数量(即连通分量的数量)。

示例图

解题思路: 将每个城市视为一个元素,遍历邻接矩阵。若 isConnected[i][j] == 1,则将城市 i 和 j 合并。最终统计连通分量的数量。

代码实现:

public int findCircleNum(int[][] isConnected) {
    if (isConnected == null || isConnected.length == 0) return 0;
    int n = isConnected.length;
    UnionFind uf = new UnionFind(n);
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (isConnected[i][j] == 1) {
                uf.union(i, j);
            }
        }
    }
    return uf.getCount();
}

class UnionFind {
    int[] root;
    int[] rank;
    int count;

    public UnionFind(int size) {
        root = new int[size];
        rank = new int[size];
        count = size;
        for (int i = 0; i < size; i++) {
            root[i] = i;
            rank[i] = 1;
        }
    }

    public int find(int x) {
        if (x == root[x]) return x;
        return root[x] = find(root[x]);
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                root[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                root[rootX] = rootY;
            } else {
                root[rootY] = rootX;
                rank[rootX] += 1;
            }
            count--;
        }
    }

    public int getCount() {
        return count;
    }
}

总结

并查集在处理动态连通性问题时非常高效。掌握路径压缩和按秩合并这两项优化技巧,能让算法性能达到理论最优。在实际开发中,遇到类似图论或分组问题,不妨优先考虑并查集方案。

目录

  1. 一、认识并查集
  2. 1. 并查集的定义
  3. 2. 基本概念
  4. 2.1. 集合的表示
  5. 2.2. 合并操作
  6. 2.3. 查询操作
  7. 3. 基本操作实现
  8. 3.1. 初始化
  9. 3.2. 查找(带路径压缩)
  10. 3.3. 合并(带按秩合并)
  11. 4. 优化技巧详解
  12. 4.1. 路径压缩
  13. 4.2. 按秩合并
  14. 5. 完整代码示例
  15. 6. 应用场景
  16. 7. 时间复杂度
  17. 二、例题实战
  18. 例题一:水位上升的泳池中游泳
  19. 例题二:省份数量
  20. 总结

更多推荐文章

查看全部
  • Java 核心面试知识点汇总:集合、多线程、JVM 及锁机制
  • Stable Diffusion WebUI 云服务器部署实战指南
  • 算法优选:位运算技巧实战解析
  • 运维排查问题常用工具与方法论
  • OpenEUICC Android eSIM 管理工具使用指南
  • 医疗 AI 场景下的 k-均值算法深度解析与实践
  • Ling Studio 实测:Ring-2.5-1T 如何优化 AI 开发工作流
  • GLM-4.6V-Flash-WEB 应用于 AIGC 内容生成质量控制
  • 近端策略优化算法 (PPO) 详解与 PyTorch 实战
  • 优雅降级 vs 渐进增强:前端兼容策略的“道”与“术”
  • Qwen3Guard-Gen-8B 内容安全模型及 Python 集成方案
  • Rust 异步测试与调试实战指南
  • llama.cpp 大模型本地部署使用介绍
  • C++ 笔试高频题解析:组合数学、BFS 与贪心调度
  • Python 爬虫入门指南:从基础到 Scrapy 框架
  • C++ 多态底层实现原理与内存布局解析
  • Node.js+Vue 在线票务系统设计与功能实现
  • Mac mini 静音低功耗配置:OpenClaw 卧室运行指南
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署与微调实践
  • 数据结构:堆的实现与应用

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online