union-find用于动态连通性问题,根据某种连通关系,将可连通的两个集合合并在与同一集合中;当查询两个元素是否连通时,即判断是否处于同一集合。用于网络连通、变量名是否重名、数学集合等。
Union-find的描述
union-find
数据结构:数组
函数:初始化函数、
unioned()
函数、find()
函数、isConnected()
函数、count()
函数用C++描述如下:
class unionFind { private: int *site; int cnt = 0; public: unionFind(int N) { } void unioned(int p, int q) { } int find(int p) { } bool isConnected(int p, int q) { } int count(void) { } };
部分函数的实现
/*构造函数,初始化*/ unionFind(int N) { cnt = N; site = new int[N]; for(int i = 0; i < N; ++i) { site[i] = i; } } /*关于unioned,先判断是否处于同一连通集合中。若是,什么也不做;若不是,则使用合并两个集合*/ /*关于find,只要将标记当前元素的集合的特征值返回即可*/ /*判断两个元素所在集合是否连通*/ bool isConnected(int p, int q) { return find(p) == find(q); } /*返回集合个数*/ int count(void) { return cnt; }
Quick-find
如果可以将 i 看作是元素,那么将site[
i ]作为标识位 i 所属的集合也是可实现的。那么
find()
函数的实现可以直接返回site[i ]的值。int find(int p) { return site[p]; }
因此
unioned()
函数可以这样实现:void unioned(int p, int q) { int locOfP = find(p); int locOfQ = find(q); if(locOfP != locOfQ) { //两个集合并成一个,减少一个集合 cnt--; //计算数组个数 int size = sizeof(site) / sizeof(site[0]); for(int i = 0; i < size; ++i) { if(site[i] == locOfQ) site[i] = locOfP; } } }
对于M次合并操作而言,每次合并都需要N+3次访问数组,也就是总共所需 M∗(N+3) 次的访问操作,如果有N个互不连通的元素都需要合并到同一个集合,那么需要N-1次合并操作,那么总访问次数就是 (N−1)∗(N+3) 次,也近似为 N2 的访问次数。
Quick-union
既然site[ i ]已经可以代表
i 所属的集合,那么直接将另一集合的值赋予site[ i ]就直接改变了i 的所属集合。将上述用树的形式描述,就是将父结点降级为另一个父结点的字节点。
unioned()
函数的实现:void unioned(int p, int q){ int rootOfP = find(p); int rootOfQ = find(q); if(rootOfP != rootOfQ) { site[rootOfQ] = rootOfP; cnt--; } }
因此
find()
也相应的需要改变:int find(int p) { //反复迭代,找到根结点 while(p != site[p]) p = site[p]; return p; }
Weighted quick-union
防止树形结构退化为链式结构,因此对根结点增加了权值。
相应的也需要增加一些数据结构,需增加一个
sz数组
(与site数组
声明方式相同),用于记录当前结点及子结点的个数(权值)。因此
unioned()
函数需修改为:void unioned(int p, int q) { int rootOfP = find(p); int rootOfQ = find(q); if(rootOfP != rootOfQ) { //树结点越多,树更可能越高 if(sz[rootOfP] > sz[rootOfQ]) { site[rootOfQ] = rootOfP; sz[rootOfP] += sz[rootOfQ]; } else { site[rootOfP] = rootOfQ; sz[rootOfQ] += sz[rootOfP]; } cnt--; } }
此外初始化函数也需要改变:
unionFind(int N) { cnt = N; site = new int[N]; for(int i = 0; i < N; ++i) site[i] = i; sz = new int[N]; for(int i = 0; i < N; ++i) sz[i] = 1; }
Path compression
可将部分结点直接上移到根结点上,从而减少
find()
函数的访问次数。对于
find()
函数的修改:int find(int p) { while(p != site[p]) { site[p] = site[site[p]]; p = site[p]; } return site[p]; }
Union-find’s ADT
class unionFind {
private:
int *site = null;
int *sz = null;
int cnt = 0;
public:
unionFind(){}
unionFind(int N) {
cnt = N;
site = new int[N];
for(int i = 0; i < N; ++i) site[i] = i;
sz = new int[N];
for(int i = 0; i < N; ++i) sz[i] = 1;
}
~unionFind() {
delete []site;
delete []sz;
}
int find(int p) {
while(p != site[p]) {
site[p] = site[site[p]];
p = site[p];
}
}
void unioned(int p, int q) {
int rootOfP = find(p);
int rootOfQ = find(Q);
if(rootOfP != rootOfQ) {
if(sz[rootOfP] > sz[rootOfQ]) {
site[rootOfQ] = rootOfP;
sz[rootOfP] += sz[rootOfQ];
} else {
site[rootOfP] = rootOfQ;
sz[rootOfQ] += sz[rootOfP];
}
cnt--;
}
}
bool isConnected(int p, int q) {
return find(p) == find(q);
}
int count(void) {
return cnt;
}
};