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

并查集数据结构详解与实战应用

并查集是一种管理不相交集合的高效数据结构,核心支持查找与合并操作。通过路径压缩和按秩合并优化,其时间复杂度接近常数。详细解析了并查集的原理、实现细节及代码示例,并结合水位上升的泳池游泳与省份数量两道经典算法题,展示了其在动态连通性问题中的实际应用。适合希望深入理解图论基础与算法优化的开发者阅读。

神经兮兮发布于 2026/3/23更新于 2026/10/579 浏览
并查集数据结构详解与实战应用

认识并查集

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

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

核心原理

集合表示

并查集通常通过一个数组(parent 数组)来表示每个元素的父节点,从而形成森林结构。每个集合用一棵树表示,树的根节点即为该集合的代表元素。初始状态下,每个元素都是一个独立的集合,父节点指向自己。

例如,有 5 个元素(0 到 4),初始 parent 数组为 [0, 1, 2, 3, 4]。

优化技巧

为了提升效率,实际应用中通常会结合两种优化策略:

  1. 路径压缩:在查找操作中,将查找路径上的所有节点直接指向根节点,减少后续查找的深度。
  2. 按秩合并:在合并操作中,总是将秩(树的高度或深度)较小的树合并到秩较大的树上,避免树变得过高。

完整代码示例

下面是一个包含路径压缩和按秩合并优化的 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);
    }
}

应用场景

并查集在处理连通性问题时非常高效,常见场景包括:

  • 图的连通性:判断图中是否存在环,或两个节点是否连通。例如 Kruskal 算法构建最小生成树时,利用并查集判断边是否会形成环。
  • 社交网络分析:判断两个用户是否属于同一个社交圈子。
  • 动态连通性问题:适用于需要频繁更新连通性状态的场景,如网络故障检测等。

实战演练

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

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

思路解析: 这道题本质上是动态连通性问题。随着时间推移,水位上升,原本不连通的平台可能变得连通。我们可以按高度从小到大排序所有格子,依次激活并合并相邻且高度小于等于当前水位的格子。一旦起点和终点连通,当前时间即为答案。

代码实现:

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 unionFind = new UnionFind(len);
    
    // 按照时间(高度)顺序处理每个格子
    for (int i = 0; i < len; i++) {
        int x = index[i] / N;
        int y = index[i] % N;
        
        // 检查四个方向的相邻格子
        for (int[] direction : DIRECTIONS) {
            int newX = x + direction[0];
            int newY = y + direction[1];
            
            if (inArea(newX, newY) && grid[newX][newY] <= i) {
                unionFind.union(index[i], getIndex(newX, newY));
            }
        }
        
        // 检查起点和终点是否连通
        if (unionFind.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;
}
例题二:省份数量

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

思路解析: 将每个城市视为一个元素,遍历邻接矩阵。如果城市 i 和城市 j 直接相连,则执行 union 操作。最终统计连通分量的数量即可。可以通过维护一个 count 变量,每次成功合并时减 1。

代码实现:

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;

    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;
        }
    }

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

    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--;
        }
    }

    int getCount() {
        return count;
    }
}

复杂度分析

  • 初始化:O(n)
  • 查找(带路径压缩):摊还复杂度接近 O(α(n)),其中 α(n) 是阿克曼函数的反函数,增长极慢,可视为常数。
  • 合并(带按秩合并):摊还复杂度同样为 O(α(n))。

这种高效的特性使得并查集在处理大规模数据时依然表现优异。

目录

  1. 认识并查集
  2. 核心原理
  3. 集合表示
  4. 优化技巧
  5. 完整代码示例
  6. 应用场景
  7. 实战演练
  8. 例题一:水位上升的泳池中游泳
  9. 例题二:省份数量
  10. 复杂度分析

更多推荐文章

查看全部
  • AI 大模型入门与实战指南:从基础原理到应用部署
  • MySQL 权限管控与 C/C++ 客户端接入实战指南
  • LangChain 链式应用实战:多种 Chain 类型详解与案例
  • Spring Boot 整合 DL4J 构建智能写作助手
  • OpenClaw:AI 行动型智能体架构与实现解析
  • Python 基础语法进阶:条件判断与循环控制
  • 软件设计各个模块分析
  • 本地部署 AI 量化分析平台:Docker 配置与波浪理论实战
  • 后端开发入门:HTML 基础与实战
  • 17 岁高中生开发 AI 应用 4 个月销售额超百万美元
  • C++ 基础概念详解
  • 大模型技术从基础入门到实战应用指南
  • Docker 容器化 Whisper:镜像选型与模型预加载实战
  • 3 个实用技术工具网站推荐:黑客模拟、威胁地图与粒子生成
  • MiGPT GUI 部署小爱音箱接入 AI 及内网穿透配置
  • 基于腾讯云 HAI 与 DeepSeek 快速搭建个人网页
  • 极化码 SC 译码算法原理、MATLAB 实现与优化策略
  • Flutter 三方库 deepyr 的鸿蒙化适配指南:构建 DaisyUI 响应式 Web 应用
  • Ubuntu 24.04 安装 ROS2 Jazzy 环境配置指南
  • 在线考试系统全流程开发实践:从数据库设计到前端交互

相关免费在线工具

  • 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