并查集
-
根节点的定义:
- 在并查集中,每个集合由一棵树表示,树的根节点是该集合的代表元素。
- 对于根节点,它的父节点指向自己,即
parent[root] == root
。
-
查找根节点:
- 当调用
find(x)
时,我们需要找到节点x
所属集合的根节点。 - 如果
parent[x] == x
,说明x
就是根节点,直接返回x
。 - 如果
parent[x] != x
,说明x
不是根节点,需要继续向上查找其父节点的根节点。
- 当调用
-
路径压缩:
- 在查找过程中,通过递归调用
find(parent[x])
,我们可以一直向上查找直到找到根节点。 - 同时,路径压缩技术会在递归返回的过程中,将路径上的所有节点直接指向根节点,从而减少未来的查找时间。
- 在查找过程中,通过递归调用
-
合并操作 (
unionSets(int x, int y)
):- 查找两个元素所属的集合的根节点。
- 如果两个根节点不同,比较它们的秩:
- 如果
rank[rootX] > rank[rootY]
,则将rootY
连接到rootX
。 - 如果
rank[rootX] < rank[rootY]
,则将rootX
连接到rootY
。 - 如果
rank[rootX] == rank[rootY]
,则任意选择一个作为新的根节点,并将新根节点的秩加1。
- 如果
通过这种方式,rank
帮助保持并查集的树结构尽可能平衡,从而提高查找和合并操作的效率。
#include <iostream>
#include <vector>
class UnionFind {
private:
std::vector<int> parent;
std::vector<int> rank; // 用于记录每个集合的“秩”(也可以理解为树的高度的上界)
public:
// 构造函数,初始化并查集
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0); // rank 数组初始化为0,表示每个节点初始时都是一棵树,高度为1。
for (int i = 0; i < n; ++i) {
parent[i] = i; // 每个元素都是自己的父节点,树根的父节点指向自己
}
}
// 查找操作,路径压缩
int find(int x) {
if (parent[x] != x) {
// 使用路径压缩技术,递归地查找元素x的根节点,并将路径上的所有节点直接指向根节点
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并操作,按秩合并
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
/*
查找两个元素所属的集合的根节点。
如果两个根节点不同,比较它们的秩:
如果 rank[rootX] > rank[rootY],则将 rootY 连接到 rootX。
如果 rank[rootX] < rank[rootY],则将 rootX 连接到 rootY。
如果 rank[rootX] == rank[rootY],则任意选择一个作为新的根节点,并将新根节点的秩加1。
*/
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; // 两个树的秩相同,新根节点的秩加1
}
}
}
// 判断两个元素是否属于同一个集合
bool connected(int x, int y) {
return find(x) == find(y);
}
};
// 示例使用
int main() {
int n = 10; // 创建一个包含10个元素的并查集
UnionFind uf(n);
// 进行合并操作
uf.unionSets(1, 2);
uf.unionSets(2, 3);
// 检查元素1和3是否在同一个集合中
std::cout << "Elements 1 and 3 are in the same set: "
<< (uf.connected(1, 3) ? "True" : "False") << std::endl;
return 0;
}
多用组合、少用继承
基于接口而非实现进行编程