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

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

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

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

认识并查集

并查集(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. 复杂度分析
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 国产开源大模型 ChatGLM3-6B 部署与使用指南
  • 6 个月免费的 ChatGPT Pro:开源维护者该怎么申请
  • ROS1 与 ROS2 桥接器完整指南:实现跨版本机器人通信
  • DeepSeek-R1 大模型基于 MS-Swift 框架的部署、推理与微调实践
  • Python 转行三大热门方向:爬虫、数据分析与 Web 开发入门指南
  • SWC:基于 Rust 的 Web 前端编译工具链解析
  • Deepfake 检测预处理:cv_resnet50_face-reconstruction 嵌入方案
  • ROS 入门实战:Linux 终端命令与基础环境搭建
  • 青少年机器人编程系统化学习路径与阶段规划
  • VSCode 远程连接 Copilot 显示脱机状态修复方案
  • 大模型训练:LLaMA-Factory 快速上手
  • ROS 机器人开发入门:第一天 Linux 终端命令与基础操作
  • 从零开始创建 cli-progress 自定义预设:打造独一无二进度条样式
  • 从 0 到 1:解决 VsCode 远程连服务器后 Github Copilot 无法使用问题
  • SGBM 半全局块匹配算法流程详解
  • C++ 实现 AVL 树:原理、旋转与代码实战
  • ROS 机器人工程师学习计划:第一天 Linux 基础与命令
  • 从冯诺依曼到操作系统:打通 Linux 底层核心逻辑
  • whisper.cpp 性能优化与编译配置指南
  • 从接口文档到前端调用:Axios 封装与实战详解

相关免费在线工具

  • 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