上篇写到集合,干脆把并查集一块写了。
一:什么是并查集
并查集是一种用途广泛的集合,也称为disjoint set,它支持一下3种操作:
1. Union(Root1, Root2):把子集合Root2并入到集合Root1中。要求Root1和Root2互不相交,否则执行合并。
2.Find(x):搜索单元素x所在的集合,并返回该集合名字。
3.UFSets(s):构造函数,将并查集中s个元素初始化为s个只有一个单元素的子集和。
二:并查集的表示
我们一般用数组和树表示,如图(图非原创):
负值表示根结点,负多少表示有多少个元素。普通结点的值为根节点下标。如图元素7,值为0,所以根节点为0号元素,它的值为-4,说明包括它该集合共4个元素。图中有
0,6,7,8。
如果想直观理解路径,可以把数组画成树的形式,类似下图。
三:并查集主要操作
并查集主要有以下操作,分别用伪代码描述,后文会给出正式代码。
1.Union
UNION(root1,root2)
parent[root1] <- parent[root1] + parent[root2]
parent[root2] <- root1
功能是对两个集合进行合并,root必须是根节点。所以我们要想造成下图这种路径压缩前的情况,必须倒着进行并集才可能产生。测试代码中会有一个错误的演示。
必须Union(1,0),Union(2,1).....Union(4,3)这样操作,每个子元素才可能把个数叠加到最终个根节点4,并且每个子元素都指向正确的parent。
2.FIND
(1).iteration
FIND(x) //iteration
while parent[x] >= 0 do
x <- parent[x]
return x
(2).recursion
FIND(x) //recursion
if parent[x] < 0 return x
else FIND(parent[x])
3.Weighted_Union
按照权重来并,我们总是把小的并到大的的根节点,采用加权规则,这样能一定程度防止并查集产生退化的树
WEIGHTED_UNION(root1,toot2)
temp <- parent[root1] + parent[root2]
if parent[root1] - parent[root2] < 0 //parent1 < parent2
parent[root1] <- temp
parent[root2] <- root1
else
parent[root2] <- temp
parent[root1] <- root2
4.压缩路径
按照折叠规则压缩路径,在包含元素idea树中搜索根,并将从i到根的路径上所有结点都变成根的子女。
COLLAPSINGFIND(i)
j <- i
while parent[j] >= 0 do
j <- parent[j]
while parent[i] ≠ j do
temp <- parent[i]
parent[i] <- j
i <- temp
四:代码实现:
#ifndef _UF_SETS_H
#define _UF_SETS_H
#include <iostream>
#include <vector>
#include <assert.h>
#include <algorithm>
#define _DEBUG_
#define _RECURSION_
const int DEFAULT_SIZE = 10;
class uf_sets {
public:
uf_sets(int sz = DEFAULT_SIZE);
~uf_sets();
public:
uf_sets& operator=(uf_sets& other);
void set_union(int root1, int root2);
int find(int x);
void weighted_union(int root1, int root2);
void collapsing_find(int i);
#ifdef _DEBUG_
void show() const;
#endif
private:
int size_;
std::vector<int> parent_;
};
uf_sets::uf_sets(int sz)
{
size_ = sz;
parent_.resize(size_);
std::fill(parent_.begin(), parent_.end(), -1);
}
uf_sets::~uf_sets()
{
}
uf_sets& uf_sets::operator=(uf_sets& other)
{
std::copy(other.parent_.begin(), other.parent_.end(), parent_.begin());
size_ = other.size_;
}
void uf_sets::set_union(int root1, int root2)
{
parent_[root1] += parent_[root2];
parent_[root2] = root1;
}
#ifndef _RECURSION_
int uf_sets::find(int x)
{
assert(x < size_);
while(parent_[x] >= 0)
x = parent_[x];
return x;
}
#else
int uf_sets::find(int x)
{
if(parent_[x] < 0)
return x;
else
find(parent_[x]);
}
#endif
void uf_sets::weighted_union(int root1, int root2)
{
int temp = parent_[root1] + parent_[root2];
if(parent_[root1]-parent_[root2] < 0){
parent_[root1] = temp;
parent_[root2] = root1;
}
else{
parent_[root2] = temp;
parent_[root1] = root2;
}
}
void uf_sets::collapsing_find(int i)
{
assert(i < size_);
int j = i;
while(parent_[j] >= 0)
j = parent_[j];
while(parent_[i] != j){
int temp = parent_[i];
parent_[i] = j;
i = temp;
}
}
void uf_sets::show() const
{
for_each(parent_.begin(), parent_.end(), [] (int n)
{ std::cout<<n<<' '; });
std::cout<<std::endl;
}
#endif
测试文件:
#include "uf_sets.h"
#include <iostream>
using namespace std;
int main()
{
#if 0
//test simple
uf_sets us(10);
us.show();
us.set_union(0, 6);
us.set_union(0, 7);
us.set_union(0, 8);
us.set_union(1, 4);
us.set_union(1, 9);
us.show();
cout<<us.find(6)<<endl;
cout<<us.find(4)<<endl;
us.weighted_union(1, 0);
us.show();
cout<<us.find(1)<<endl;
#endif
//test collapsingfind
uf_sets us(10);
/*
//error
us.set_union(0, 6); //cann't set union do like this, see set_union function !
us.set_union(0, 7); //正向Union无法产生路径压图中长路径,因为Union函数不支持,它是针对两个root实现的
us.set_union(0, 8);
us.set_union(7, 1);
us.set_union(7, 9);
us.set_union(9, 3);
us.set_union(9, 5);
*/
us.set_union(9, 5);
us.set_union(9, 3);
us.set_union(7, 9);
us.set_union(7, 1);
us.set_union(0, 6);
us.set_union(0, 7);
us.set_union(0, 8);
us.show();
us.collapsing_find(5);
us.show();
return 0;
}
更详细解释参考:并查集详解 (转),一个很幽默风趣的解释。