并查集
1 概念介绍
在计算机科学中,并查集是一种树形结构,用来处理不交集(Disjoint Sets)得合并和查询问题[1]。并查集主要是有两个操作,依次是:
- Find : 查找
- Union :合并
其中Find是判断给定元素属于哪一类别,常用一组类别中某个元素作为Root元素,表征这一类别,那么Find操作其实就是这个元素对应的Root值为多少。两个元素的Root值相同,则该两个元素为同一类别。
在构建并查集时,会遇到将两个类别合并为一个类别,即为Union。按上述对于Find操作的描述,即将原两个类别中所有元素对应的Root值改成同一值。
2 代码模板
2.1 朴素并查集
不考虑效率,从定义上实现并查集Find和Union两个操作。核心点就是用一个p数组维护该元素的父节点元素值或Root值(路径压缩),当一个元素的p值为其本身时,则该节点为Root节点。
其中p值为元素对应的Root时并查集效率
更高,以下模板给出了这种情况下的算法实现。
void MakeSet(int size)
{
// 初始化并查集,将每个元素的p值设置为其本身
// 假设节点值是1~size(含size)
for (int i = 1; i <= size; i++) {
p[i] = i;
}
}
int Find(int n)
{
// 判断元素n的类别,即查找元素n的Root元素值
if (p[n] != n) {
p[n] = Find(p[n]); // 非Root节点,继续向上查找,并赋值
}
return p[n];
}
void Union(int n, int m)
{
// 将元素n和元素m所在类别合并
// 只需将n和m中的一个元素的Root指向另一个元素的Root
int rootA = Find(n);
int rootB = Find(m);
p[rootA] = rootB; // n的Root指向m的Root
}
2.2 维护元素集Size的并查集
与朴素并查集算法实现不用的是,这里维护了一个size数组保存每个类别中的当前的元素数量,预设只有Root节点的size数组值才有效。
void MakeSet(int size)
{
// 初始化并查集,将每个元素的p值设置为其本身
// 假设节点值是1~size(含size)
for (int i = 1; i <= size; i++) {
p[i] = i;
// ! 增加size数组
size[i] = 1;
}
}
int Find(int n)
{
// 判断元素n的类别,即查找元素n的Root元素值
// ! Find操作是不需更新size数组
if (p[n] != n) {
p[n] = Find(p[n]); // 非Root节点,继续向上查找,并赋值
}
return p[n];
}
void Union(int n, int m)
{
// 将元素n和元素m所在类别合并
// 只需将n和m中的一个元素的Root指向另一个元素的Root
int rootA = Find(n);
int rootB = Find(m);
p[rootA] = rootB; // n的Root指向m的Root
// ! rootB为新的Root, 只需更新rootB的size值
size[rootB] += size[rootA];
}
2.3 维护元素到root节点距离的并查集
用一个数组维护当前元素到root节点的距离。
void MakeSet(int size)
{
// 初始化并查集,将每个元素的p值设置为其本身
// 假设节点值是1~size(含size)
for (int i = 1; i <= size; i++) {
p[i] = i;
w[i] = 0; // 默认每个节点的root节点为其本身,故w值默认为0
}
}
int Find(int n)
{
// 判断元素n的类别,即查找元素n的Root元素值
if (p[n] != n) {
int num = Find(p[n]);
w[n] += w[num]; // 更新当前节点的w值
p[n] = num; // 非Root节点,继续向上查找,并赋值
}
return p[n];
}
void Union(int n, int m)
{
// 将元素n和元素m所在类别合并
// 只需将n和m中的一个元素的Root指向另一个元素的Root
int rootA = Find(n);
int rootB = Find(m);
p[rootA] = rootB; // n的Root指向m的Root
w[rootA] = distnace; // 具体情况具体分析
}
相关leetcode例题
[1] 并查集-维基百科
[2] 并查集 OI Wiki