并查集
概念
把n个不同的元素的集合划分为若干个等价类时,先把每一个对象看作是一个单元素集合,然后依次将属于同一等价类的元素所在的集合合并。在此过程中将反复地使用一个查找运算,确定一个元素在哪一个集合中。能够完成这种功能的集合就是并查集,它支持以下三种操作:
- Ufsets(n):构造函数,将并查集中n个元素初始化为n个只有一个单元素的子集合。
- Union(S1,S2):把集合S2并入集合S1中。要求S1与S2互不相交,否则没有结果。
- Find(d):查找单元素d所在的集合,并返回该集合的名字。
对于并查集来说,每个子集合(等价类)用一棵树表示,子集合中每个元素用树中的一个结点表示。为了处理简单,用树的根结点来代表相应的等价类集合。
在此,等价类树用双亲表示法表示。此外,树的根结点的双亲域设为-k,k是该树的结点数:
并查集的合并
(a)表示将S2合并到S1中的操作
(b)表示将S1合并到S2中的操作
简单的函数实现
并查集中元素结点的定义
// 并查集元素结点类
template <class ElemType>
struct ElemNode
{
// 数据成员:
ElemType data; // 数据域
int parent; // 双亲域
};
并查集类模板的定义
// 并查集
template <class ElemType>
class Equivalence
{
protected:
// 并查集的数据成员:
ElemNode<ElemType> *set; // 存储结点的双亲
int size; // 结点个数
// 辅助函数
int Find(ElemType e) const; // 查找元素e所在等价类的根
public:
// 抽象数据类型方法声明及重载编译系统默认方法声明:
Equivalence(ElemType es[], int sz); // 构造sz个单结点树(等价类)
virtual ~Equivalence(); // 析构函数
ElemType GetElem(int p)const;
int GetOrder(ElemType e)const;
void Union(ElemType a, ElemType b); // 合并a与b所在的等价类
bool Differ(ElemType a, ElemType b); // 如果a与b不在同一棵树上,返回true,否则返回false
Equivalence(const Equivalence ©); // 复制构造函数
Equivalence &operator =(const Equivalence ©); // 赋值运算符
};
并查集类模板部分函数的实现
构造函数
template <class ElemType>
Equivalence<ElemType>::Equivalence(ElemType es[], int sz)
// 操作结果:构造sz个单结点树(等价类)
{
size = sz; // 容量
set = new ElemNode<ElemType>[size]; // 分配空间
for (int i = 0; i < size; i++) {
set[i].data = es[i];
set[i].parent = -1; // 每个结点构成单结点树形成的等价类
}
}
查找结点p所在数的根
template <class ElemType>
int Equivalence<ElemType>::Find(ElemType e) const
// 操作结果:查找结点p所在树的根
{
int p = 0;
while (p < size && set[p].data != e)
p++;
if (p == size)
throw Error("元素不存在!"); // 抛出异常
while (set[p].parent > -1) p = set[p].parent;// 查找根
return p; // 返回根
}
合并A与B所在的等价类
template <class ElemType>
void Equivalence<ElemType>::Union(ElemType a, ElemType b)
// 操作结果:合并a与b所在的等价类
{
int root1 = Find(a); // 查找a所在树(等价类)的根
int root2 = Find(b); // 查找b所在树(等价类)的根
if (root1 != root2) {
set[root1].parent += set[root2].parent;//树的根结点的双亲域该树的结点数
set[root2].parent = root1; // 合并树(等价类)
}
}
拷贝构造函数及重载赋值运算符
template <class ElemType>
Equivalence<ElemType>::Equivalence(const Equivalence ©)
// 操作结果:由copy构造新对象——复制构造函数
{
size = copy.size; // 容量
set = new ElemNode<ElemType>[size]; // 分配空间
for (int i = 0; i < size; i++) {
set[i].parent = copy.set[i].parent; // 复制parent的每个元素
set[i].data = copy.set[i].data; // 复制parent的每个元素
}
}
template <class ElemType>
Equivalence<ElemType> &Equivalence<ElemType>::operator=(const Equivalence<ElemType> ©)
// 操作结果:将copy赋值给当前对象——赋值运算符
{
if (© != this) {
size = copy.size; // 容量
delete []set; // 释放空间
set = new ElemNode<ElemType>[size]; // 分配空间
for (int i = 0; i < size; i++) {
set[i].parent = copy.set[i].parent; // 复制parent的每个元素
set[i].data = copy.set[i].data; // 复制parent的每个元素
}
}
return *this;
}
输出该并查集里的所有等价类
bool out[n]; // 已输出的结点值为true,否则值为false
for (i = 0; i < n; i++)
out[i] = false;
int p = 0; // 当前结点
while (p < n) { // 对没有输出的当前结点,输出其等价类
cout << "{" << e.GetElem(p);
out[p] = true;
for (i = p + 1; i < n; i++) { // 输出等价类
if (!e.Differ(e.GetElem(p), e.GetElem(i))) { // p与i在同一个等价类中
cout << "," << e.GetElem(i);
out[i] = true;
}
}
cout << "}" << endl;
while (p < n && out[p]) p++;
}