并查集的抽象数据类型
并查集是一个包含若干个结点的集合,结点之间的关系有连通和断开两种。
连通关系具有传递性:即A与B连通,B与C连通,则A与C连通。
由于连通关系的传递性,所有连通的结点构成一个连通集,如果一个并查集没有连通集,则该并查集是一个空集;如果一个并查集只有一个连通集,则该并查集全局连通;如果一个并查集有n个连通集,则该并查集被分割成n个部分。
连通集的抽象数据类型
一个连通集内的结点可以用树结构描述,每个结点有两个域:
数据域:存储结点自身信息
指针域:储存父结点位置
注意,根结点的指针域不存储父结点位置,而是存储连通集的结点总数,原因在后文给出。
连通集的表示
连通集可以用其根结点表示,如某连通集 U{0, 1, 4, 6} 的根结点为0,则记连通集 U{0, 1, 4, 6} 为U(0)。
并查集的基本操作
Create 创建一个空的并查集
Find(X) 在并查集中查找X结点,返回X结点属于哪个集合
Union(X,Y) 连接X和Y结点
Check(X,Y) 检查X和Y结点是否连通
Check 检查全局连通性
并查集:数组实现
设一个并查集UnionFindSet有MaxSize个结点,用数组array_father[MaxSize]描述结点信息:
第n个结点的父节点位置为array_father[n],故不需要数据域。
template<int MaxSize>
class UnionFindSet {
private:
int array_father[MaxSize]; //指针域
int find(int x); //在并查集中查找X结点,返回X结点属于哪个集合
public:
void Union(int x, int y); //连接X和Y结点
bool check(int x, int y); //检查X和Y是否连通
void check()const; //检查全局连通性
};
连接X和Y结点
连接X和Y结点,相当于连接X和Y结点所在的连通集。前文提到:连通集可以用其根结点表示,故可以直接使U(X)的根结点指向U(Y)的根结点,或使U(Y)的根结点指向U(X)的根结点,那么到底让X指向Y还是让Y指向X呢?
前文提到:连通集内的结点是用树结构描述的,树的查找效率和树的规模有关,树的规模可以用树的高度或树的结点数描述,因为连通集的查找带有路径压缩(后文会提到),连通集所对应树的高度不会超过2,所以本文用树的结点数描述树的规模。
为了不使树的规模变得太大,应尽量将小树插入到大树上时。
template<int MaxSize>
void UnionFindSet<MaxSize>::Union(int x, int y) {
int root[2] = { find(x),find(y) }; //查找X和Y结点所在连通集的根结点
//如果没有找到X或Y的根结点,则X或Y不属于任何连通集
if (EOF == root[0] || EOF == root[1] || root[0] == root[1])return;
//如果U(Y)的规模较大,则把U(X)连通到U(Y)上
else if (array_father[root[0]] > array_father[root[1]]) {
array_father[root[1]] += array_father[root[0]];
array_father[root[0]] = root[1];
}
//如果U(X)的规模较大,则把U(Y)连通到U(X)上
else {
array_father[root[0]] += array_father[root[1]];
array_father[root[1]] = root[0];
}
}
检查X和Y结点是否连通
检查X和Y结点是否连通,相当于检查X和Y是否属于同一个连通集,相当于检查X和Y所在连通集的根结点是否相同。
template<int MaxSize>
bool UnionFindSet<MaxSize>::check(int x, int y)const {
//如果X或Y越界,则X或Y不属于任何连通集
if (x < 0 || x >= MaxSize || y < 0 || y >= MaxSize)return false;
//查找X和Y结点所在连通集的根结点,检查它们是否相同
return find(x) == find(y) ? true : false;
}
查找结点所在的集合:带有路径压缩
连通集内的结点是用树结构描述的,我们自然希望树的高度越矮越好,最好连通集的所有结点都直接连到根结点上。
当我们查找某个X结点时,它可能不是直接连在根结点上的,这时我们需要“记住”这个结点,然后继续访问X结点的父结点。
如果父结点也不是直接连到根结点上的,我们又“记住”父结点,然后继续访问父结点的父结点,以此类推。
当查找到根结点时,我们先把“记住”的所有结点的父结点设置成根结点,再返回根结点,这样X结点到根结点经过的所有结点,到根结点的路径都变成了1,这就完成了一次路径压缩。
那怎么“记住”途径的结点呢,我们用堆栈实现。
template<int MaxSize>
int UnionFindSet<MaxSize>::find(int x) {
//如果X越界,则X属于任何连通集
if (x < 0 || x >= MaxSize)return EOF;
stack<int>S; //建立存储结点的堆栈
//当X结点的父结点不是根结点时循环
while (array_father[x] >= 0) {
S.push(x); //将途径结点压入堆栈
x = array_father[x]; //继续访问途径结点的父结点
}
//把堆栈里所有结点的父结点设为根结点
while (!S.empty())
array_father[S.pop()] = x;
return x; //返回根结点
}
检查全局连通性
当全局连通时,只存在一个连通集,也就是所有节点中,只有一个根结点。
template<int MaxSize>
void UnionFindSet<MaxSize>::check()const {
const int*it = array_father;
int sum = 0; //变量sum记录一共有几个根结点
//全局扫描,统计根结点数量
for (int count = 0; count != MaxSize; count++)
if (*it++ < 0)sum++;
//当只有一个根结点时,返回提示信息:全局连通
//否则返回提示信息:全局有N个连通集
if (1 == sum)cout << "The network is connected." << endl;
else cout << "There are " << sum << " compenents." << endl;
}
完整代码
//并查集:数组实现
#ifndef OCUNIONFINDSET_H
#define OCUNIONFINDSET_H
#include<iostream>
using std::ostream;
using std::cout;
using std::endl;
#include"OCstack.h"
using OC::stack;
#ifndef _OC_BEGIN
#define _OC_BEGIN namespace OC{
#endif
#ifndef _OC_END
#define _OC_END }
#endif
#ifndef EOF
#define EOF -1
#endif
_OC_BEGIN
template<int MaxSize>
class UnionFindSet {
private:
int array_father[MaxSize]; //指针域
int find(int x); //查找某个元素所在的集合
public:
template<int _MaxSize>
friend ostream&operator<<(ostream&output, UnionFindSet<_MaxSize> const&x);
UnionFindSet(); //构造函数
void Union(int x, int y); //对X和Y所在的集合并运算
bool check(int x, int y); //检查X和Y是否连通
void check()const; //检查全局连通性
};
//查找某个元素所在的集合:带有路径压缩
template<int MaxSize>
int UnionFindSet<MaxSize>::find(int x) {
if (x < 0 || x >= MaxSize)return EOF;
stack<int>S;
while (array_father[x] >= 0) {
S.push(x);
x = array_father[x];
}
while (!S.empty())
array_father[S.pop()] = x;
return x;
}
template<int MaxSize>
ostream&operator<<(ostream&output, UnionFindSet<MaxSize> const&x) {
const int*it = x.array_father;
for (int count = 0; count != MaxSize; count++)
output << '[' << count << ':' << *it++ << ']';
return output;
}
//构造函数
template<int MaxSize>
UnionFindSet<MaxSize>::UnionFindSet() {
int*it = array_father;
for (int count = 0; count != MaxSize; count++)
*it++ = -1;
}
//对X和Y所在的集合并运算
template<int MaxSize>
void UnionFindSet<MaxSize>::Union(int x, int y) {
int root[2] = { find(x),find(y) };
if (EOF == root[0] || EOF == root[1] || root[0] == root[1])return;
else if (array_father[root[0]] > array_father[root[1]]) {
array_father[root[1]] += array_father[root[0]];
array_father[root[0]] = root[1];
}
else {
array_father[root[0]] += array_father[root[1]];
array_father[root[1]] = root[0];
}
}
//检查X和Y是否连通
template<int MaxSize>
bool UnionFindSet<MaxSize>::check(int x, int y)const {
if (x < 0 || x >= MaxSize || y < 0 || y >= MaxSize)return false;
return find(x) == find(y) ? true : false;
}
//检查全局连通性
template<int MaxSize>
void UnionFindSet<MaxSize>::check()const {
const int*it = array_father;
int sum = 0;
for (int count = 0; count != MaxSize; count++)
if (*it++ < 0)sum++;
if (1 == sum)cout << "The network is connected." << endl;
else cout << "There are " << sum << " compenents." << endl;
}
_OC_END
#endif