并查集(Union-Find)是一种用于处理集合合并及查询操作的数据结构,广泛应用于解决图的连通性、动态连通性等问题。并查集通过提供两个基本操作:Find(查找)和 Union(合并),能够高效地判断和合并元素所属的集合。在一些简单的实现中,我们甚至不使用“按秩合并”(rank),只使用基本的路径压缩技术来优化性能。
并查集的基本概念
并查集主要有两个操作:
- Find:用于查找一个元素所在的集合,即找出该元素的“根节点”。
- Union:用于将两个元素所在的集合合并成一个集合。
路径压缩(Path Compression)
在进行Find
操作时,我们将沿途的每个节点直接连接到根节点,这样下一次查询时,查找路径更短,从而加速后续的查询。
简单的并查集实现(不使用秩)
在这里,我们将提供一个简化版本的并查集,不使用按秩合并(即不使用rank数组)。该版本在合并两个集合时只是简单地将一个集合的根节点指向另一个集合的根节点。虽然这种方法在某些情况下可能导致树的深度较大,但它依然能够正确地处理并查集问题。
使用家族问题来解释并查集
我们通过一个简单的家族问题来说明并查集的应用:
问题描述:
假设你有一个家族中的若干个成员,每个成员可以与其他成员建立亲属关系(例如父子、兄弟姐妹、夫妻等)。我们希望能够高效地判断两个成员是否属于同一个家族。
在这个问题中,我们可以使用并查集来管理家族成员的关系:
- 每个成员可以看作一个元素,初始时每个成员自成一组。
- 当两个成员建立关系时,我们可以通过
Union
操作将这两个成员所在的集合合并。 - 查询两个成员是否属于同一家族时,我们只需通过
Find
操作判断他们是否属于同一个集合。
并查集的简化实现(不使用秩)
Java 实现
class UnionFind {
private int[] parent;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i; // 初始化,每个成员的父节点是自己
}
}
// 查找操作,路径压缩
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) {
parent[rootX] = rootY; // 合并两个家族
}
}
}
public class Main {
public static void main(String[] args) {
UnionFind uf = new UnionFind(5); // 5 个家族成员
uf.union(0, 1); // 成员 0 和成员 1 成为同一个家族
uf.union(1, 2); // 成员 1 和成员 2 成为同一个家族
System.out.println(uf.find(0) == uf.find(2)); // 输出 true,成员 0 和成员 2 是同一家族
}
}
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
private:
vector<int> parent;
public:
UnionFind(int size) {
parent.resize(size);
for (int i = 0; i < size; i++) {
parent[i] = i; // 每个成员的父节点是自己
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
void unionSet(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY; // 合并两个家族
}
}
};
int main() {
UnionFind uf(5); // 5 个家族成员
uf.unionSet(0, 1); // 成员 0 和成员 1 成为同一个家族
uf.unionSet(1, 2); // 成员 1 和成员 2 成为同一个家族
cout << (uf.find(0) == uf.find(2)) << endl; // 输出 1(true),成员 0 和成员 2 是同一家族
return 0;
}
Python 实现
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 每个成员初始化为自己为父节点
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # 路径压缩
return self.parent[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.parent[rootX] = rootY # 合并两个家族
# 使用示例
uf = UnionFind(5)
uf.union(0, 1)
uf.union(1, 2)
print(uf.find(0) == uf.find(2)) # 输出 True,成员 0 和成员 2 是同一家族
JavaScript 实现
class UnionFind {
constructor(size) {
this.parent = Array.from({ length: size }, (_, i) => i);
}
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]); // 路径压缩
}
return this.parent[x];
}
union(x, y) {
let rootX = this.find(x);
let rootY = this.find(y);
if (rootX !== rootY) {
this.parent[rootX] = rootY; // 合并两个家族
}
}
}
// 使用示例
let uf = new UnionFind(5);
uf.union(0, 1);
uf.union(1, 2);
console.log(uf.find(0) === uf.find(2)); // 输出 true,成员 0 和成员 2 是同一家族
总结
并查集是一个非常高效的解决动态连通性问题的数据结构。通过路径压缩和按秩合并等技术,我们可以实现高效的查询和合并操作。虽然在一些简化的版本中,我们可能不使用秩(rank)来优化树的高度,但使用路径压缩仍然能够在大多数情况下保持较好的性能。
,能够为我们提供高效的解法。