并查集

并查集:(union-find sets) 是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskal算法求最小生成树。

并查集的精髓:

1、make_set(x) 把每一个元素初始化为一个集合

初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。

2、find_set(x) 查找一个元素所在的集合(有两种方法:朴素查找和采用路径压缩的方法查找,其中路径压缩有递归和非递归)

查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

3、Union(x,y) 合并x,y所在的两个集合:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

并查集的优化

1、find_set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次find_set(x)都是O(n)的复杂度,因此需要路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次find_set(x)时复杂度就变成O(1)了,路径压缩方便了以后的查找。

2、Union(x,y)时按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。

View Code
const int size = 100;
int father[size];
int rank[size];

//把每个元素初始化为一个集合
void makes_set()
{
for(int i = 1; i <= n; i ++){
father[i] = i;
rank[i] = 1;
}
return ;
}

//查找一个元素所在的集合,找到这个元素所在集合的祖先,这
//是并查集判断和合并的最终依据。判断两个元素是否属于同一个集合,只要看它们所在集合的祖先是否相同
int find_set(int x)
{
if(x != father[x]){
father[x] = find_set(father[x]);
}
return father[x];
}

//非递归路径压缩
int find_set(int x)
{
int r = x;
while(r != father[r]){ //查找根节点
r = father[r]; //找到根节点,用r记录
}

int k = x;
while(k != r){ //非递归路径压缩
j = father[k]; //用j暂存k的父节点
father[k] = r; //k指向根节点
k = j; //k移到父节点
}
return r; //返回根节点的值
}

//利用find_set找到两个集合的祖先,将一个集合的祖先指向另一个集合的祖先
void Union(int x, int y)
{
x = find_set(x);
y = find_set(y);
if(x == y){
return ;
}
if(rank[x] < rank[y]){
father[x] = y;
}
else{
if(rank[x] == rank[y]){
rank[x] ++;
}
else{
father[y] = x;
}
return ;
}
posted @ 2012-03-26 19:55  爱也玲珑  阅读(367)  评论(0编辑  收藏  举报