不相交集合用于对集合的一种操作,对不相交集合主要四个主要点和两个关键词:
1. 集合的表示方法
2. 创建集合
3. 获得某个元素所在集合的代表
4. 合并两个集合
两个关键词
1. 按秩合并
2. 路径压缩
集合的表示方法
链表表示法和有根数表示法。
链表表示法
链表表示方法,它是最简单的表示方法,也是最容易想到的方法。它的其他三个主要点的操作如下:
获得某个元素所在集合的代表
为了能够在O(1)时间内获得代表,集合的每个元素都可以增加一个卫星数据来指向代表;
合并两个集合
为了能够在O(1)时间内将一个集合链表添加到另一个集合链表的后面,我们可以增加一个指针指向集合链表的尾,这样可以O(1)时间内找到链表最后一个元素;
由于添加到后面的集合都要重新设置它所有元素的代表为第一个集合的代表,所以一种优化就是将长度短的集合链表添加到长度长的集合链表后面;
创建集合
创建只有一个节点的集合,设置这个节点为代表,设置这个节点为尾,设置链表长度为1;
在O(1)时间内完成;
有根数表示法
有根数是目前已知的最快的方法,它主要有两个特殊的操作:
按秩合并
和链表表示法的合并操作的优化方法差不多,只是这里的秩是树的高度的意思;
将高度较小的一个集合树合并到高度较高的集合树中;
路径压缩
它的思路是,假设集合A合并到集合B ,只是将集合A的代表指向B的代表,集合A中的其他元素的代表不作更改,这样就可以O(1)时间内完成合并。那什么时候,更改集合A的其他元素的代表呢?当执行“获得某个元素所在集合的代表”操作时。
参考代码部分的find_set函数,如果该元素e不是它记录的代表p[e],那么重新设置它记录的代表p[e],这个是可以在O(1)时间内完成的,因为p[e]可能是集合A合并前的代表也可能是合并后的代表。如果是合并前的代表,那么两个递归后,就可以将p[e]设置为集合B的代表;如果是合并后的代表,那么一个递归后就返回,并将p[e]设置为集合B的代表(当然这个递归是无用功)。
代码
该代码,假设集合是由顶点组成class vertex
{
public:
vertex(){
make_set();
}
vertex * find_set(){
if(mParent != this){
mParent = mParent->find_set();
}
return mParent;
}
void union_set(vertex * v){
link(v->find_set());
}
private:
vertex * mParent;
int mRank;
void make_set(){
mParent = this;
mRank = 0;
}
void link(vertex * v){
if(mRank < v->mRank){
mParent = v->find_set();
}else{
v->mParent = this;
if(mRank == v->mRank){
mRank++;
}
}
}
};