数据结构——并查集


1 基本介绍

并查集(Union-Find)是一种 树型 的数据结构,用于处理一些不交集的 查询合并 问题。表面上它很高大上,但是实现起来倒不算难。

它可以拿如下这张图来理解(下图中有两个不同的集合,一个集合以 1 为根节点,另一个集合以 7 为根节点):
alt text

2 基本操作

2.1 查找 ( Find )

2.1.1 定义

获取一个节点的根节点。它经常被用来检查两个节点是否属于同一子集。

2.1.2 实现思路

如果该元素就是根节点,则直接返回该元素自己;否则就查找该元素的父节点的根节点。

2.1.3 举例

对于基本介绍中的并查集,查找节点 1, 2, 3, 4, 5 的根节点的结果为节点 1;查找节点 6, 7, 8, 9, 10, 11 的根节点的结果为节点 7

2.2 合并 ( Union )

2.2.1 定义

将两个子集合并成同一个集合。

2.2.2 实现思路

让一个子集的根节点 指向 另一个子集的根节点。

2.2.3 举例

对于基本介绍中的并查集,如果将节点 2, 7 所在的子集合并(此处让 1 指向 7),则会得到如下的并查集:
alt text

3 基础实现

public class UnionFind {
    private final int[] parent; // parent[i] 表示第 i 个元素的父节点

    // 初始化并查集
    public UnionFind(int size) {
        parent = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点都是自己
        }
    }

    // 查找 x 的根节点
    public int find(int x) {
        if (x != parent[x]) { // 如果 x 不是根节点
            return find(parent[x]); // 则寻找 x 的根节点并返回
        }
        return x; // 否则直接返回 x 自己
    }

    // 合并两个节点所在的集合
    public void union(int x, int y) {
        parent[find(x)] = find(y); // 让 x 的根节点 指向 y 的根节点
    }

    // 测试用例
    public static void main(String[] args) {
        UnionFind uf = new UnionFind(10);
        uf.union(1, 2);
        uf.union(3, 4);
        uf.union(2, 3);

        System.out.println(uf.find(1) == uf.find(4)); // 输出 true,因为 1 和 4 现在属于同一个集合
    }
}

注意,本文的实现假设元素是从 0size - 1 的连续整数。如果需要处理 非连续自定义类型 的元素,可以使用映射(如 HashMap)来将元素映射到这些整数索引上。

4 性能优化

以下给出两种主要的优化思路:

4.1 路径压缩

4.1.1 核心思想

在查找过程中,将路径上的所有节点都直接连接到根节点上。从而减少树的高度,加快后续查找速度。

4.1.2 实现方式

在执行查找操作时,如果当前节点的父节点不是根节点,则先将父节点连接到根节点上,然后再继续向上查找。

4.2 按秩合并

4.2.1 核心思想

(Rank)可以理解为 树的高度的一个上界。在合并两个集合时,将 秩较小的集合 合并到 秩较大的集合 中,以保持树的相对平衡。

4.2.2 实现方式

  • 在并查集的每个节点上添加一个额外的属性(如 rank),用于记录以该节点为根的子树的高度上界。
  • 在合并操作时,比较两个集合的秩,将秩较小的集合合并到秩较大的集合中,并更新合并后集合的秩(如果两个集合的秩相等,则合并后集合的秩加 1)。

5 优化后的实现

public class UnionFind {
    private final int[] parent; // parent[i] 表示第 i 个元素的父节点
    private final int[] rank; // rank[i] 表示以 i 为根的树的深度(或节点数)

    // 初始化并查集
    public UnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点都是自己
            rank[i] = 1;   // 初始时,每个元素都是独立的树,深度为 1
        }
    }

    // 查找 x 的根节点
    public int find(int x) {
        if (x != parent[x]) { // 如果 x 不是根节点,先更新 x 的根节点
            // 则使用路径压缩,将 x 的 父节点 直接指向 根节点
            parent[x] = find(parent[x]);
        }
        return parent[x]; // 然后返回 x 的根节点
    }

    // 合并两个节点所在的集合
    public void union(int x, int y) {
        int rootX = find(x); // 获取 x 的根节点
        int rootY = find(y); // 获取 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]++; // 并让被合并的集合的秩加一
            }
        }
    }

    // 测试用例
    public static void main(String[] args) {
        UnionFind uf = new UnionFind(10);
        uf.union(1, 2);
        uf.union(3, 4);
        uf.union(2, 3);

        System.out.println(uf.find(1) == uf.find(4)); // 输出 true,因为 1 和 4 现在属于同一个集合
    }
}

6 适用场景

6.1 社交网络中的好友关系

  • 应用场景:在社交网络中,人与人之间存在着好友关系。通过并查集可以方便地建立和查询好友圈关系。
  • 实现方式:将每个人看作一个节点,利用并查集建立这些节点之间的关系。当两个人成为好友时,将它们所在的两个集合进行合并操作。通过查询某两个人是否属于同一个集合,就可以判断他们是否在同一个好友圈中。

6.2 地图中的路径判断

  • 应用场景:在电子地图中,经常需要判断两个地点之间是否存在路径。
  • 实现方式:将地图上的每个地点看作一个节点,利用并查集来表示各个地点的连接关系。当两个地点之间存在路径时,将它们所在的两个集合进行合并操作。通过查询两个地点是否属于同一个集合,就可以判断它们之间是否存在路径。

6.3 互联网网络连接

  • 应用场景:在互联网网络中,经常需要处理网络连接的问题。
  • 实现方式:将网络中的每个节点看作一个机器或者设备,利用并查集来表示网络中各个节点的连接关系。通过对两个节点进行合并操作,将它们所在的集合合并为一个集合,从而建立网络连接。通过查询两个节点是否属于同一个集合,就可以判断它们之间是否存在网络连接。

6.4 岛屿数量统计与区域合并

  • 应用场景:在地理学和计算机视觉领域,经常需要统计岛屿数量以及合并区域。
  • 实现方式:将地图上的每个陆地区域看作一个节点,利用并查集来表示各个区域的连通性。通过查询不同集合的数量,就可以统计岛屿的数量。同时,通过对区域进行合并操作,可以实现区域的合并。

6.5 等式方程的可满足性

  • 应用场景:给定一个由表示变量之间关系的字符串方程组成的数组,需要判断是否存在一种整数分配方式,使得所有给定的方程都得到满足。
  • 实现方式:使用并查集求解,先处理等号情况,将等号两边元素所在的集合并到一起;后处理不等号方程,使用查操作,找到不等号两边的元素是否为同一集合,若为同一集合则证明该方程不满足,返回false,若所有的不等号方程都满足,则返回true。

7 总结

并查集作为一种 简单但十分实用 的数据结构,在解决集合相关的问题上具有 很高的效率和便利性。其应用场景不仅限于上述几个领域,还包括 动态连通性问题最小生成树算法 等多个方面。

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值