文章目录
- [1 基本介绍](#1 基本介绍)
- [2 基本操作](#2 基本操作)
-
- [2.1 查找 ( Find )](#2.1 查找 ( Find ))
-
- [2.1.1 定义](#2.1.1 定义)
- [2.1.2 实现思路](#2.1.2 实现思路)
- [2.1.3 举例](#2.1.3 举例)
- [2.2 合并 ( Union )](#2.2 合并 ( Union ))
-
- [2.2.1 定义](#2.2.1 定义)
- [2.2.2 实现思路](#2.2.2 实现思路)
- [2.2.3 举例](#2.2.3 举例)
- [3 基础实现](#3 基础实现)
- [4 性能优化](#4 性能优化)
-
- [4.1 路径压缩](#4.1 路径压缩)
-
- [4.1.1 核心思想](#4.1.1 核心思想)
- [4.1.2 实现方式](#4.1.2 实现方式)
- [4.2 按秩合并](#4.2 按秩合并)
-
- [4.2.1 核心思想](#4.2.1 核心思想)
- [4.2.2 实现方式](#4.2.2 实现方式)
- [5 优化后的实现](#5 优化后的实现)
- [6 适用场景](#6 适用场景)
-
- [6.1 社交网络中的好友关系](#6.1 社交网络中的好友关系)
- [6.2 地图中的路径判断](#6.2 地图中的路径判断)
- [6.3 互联网网络连接](#6.3 互联网网络连接)
- [6.4 岛屿数量统计与区域合并](#6.4 岛屿数量统计与区域合并)
- [6.5 等式方程的可满足性](#6.5 等式方程的可满足性)
- [7 总结](#7 总结)
1 基本介绍
并查集 (Union-Find)是一种 树型 的数据结构,用于处理一些不交集的 查询 及 合并 问题。表面上它很高大上,但是实现起来倒不算难。
它可以拿如下这张图来理解(下图中有两个不同的集合,一个集合以 1
为根节点,另一个集合以 7
为根节点):
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
),则会得到如下的并查集:
3 基础实现
java
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 现在属于同一个集合
}
}
注意,本文的实现假设元素是从 0
到 size - 1
的连续整数。如果需要处理 非连续 或 自定义类型 的元素,可以使用映射(如 HashMap
)来将元素映射到这些整数索引上。
4 性能优化
以下给出两种主要的优化思路:
4.1 路径压缩
4.1.1 核心思想
在查找过程中,将路径上的所有节点都直接连接到根节点上。从而减少树的高度,加快后续查找速度。
4.1.2 实现方式
在执行查找操作时,如果当前节点的父节点不是根节点,则先将父节点连接到根节点上,然后再继续向上查找。
4.2 按秩合并
4.2.1 核心思想
秩 (Rank)可以理解为 树的高度的一个上界。在合并两个集合时,将 秩较小的集合 合并到 秩较大的集合 中,以保持树的相对平衡。
4.2.2 实现方式
- 在并查集的每个节点上添加一个额外的属性(如
rank
),用于记录以该节点为根的子树的高度上界。 - 在合并操作时,比较两个集合的秩,将秩较小的集合合并到秩较大的集合中,并更新合并后集合的秩(如果两个集合的秩相等,则合并后集合的秩加
1
)。
5 优化后的实现
java
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 总结
并查集作为一种 简单但十分实用 的数据结构,在解决集合相关的问题上具有 很高的效率和便利性 。其应用场景不仅限于上述几个领域,还包括 动态连通性问题 、最小生成树算法 等多个方面。