并查集

一篇并查集笔记整理。

并查集适用场合:在一些应用问题中,需要将n个不同的元素划分为一组不相交的集合。开始时,每个元素自成一个单元集合,然后按一定的规律将归于同一组的集合合并。在此过程中要反复用到查询某个元素属于哪个集合的运算

并查集的定义及其实现

并查集是一种简单的用途广泛的集合,它支持以下3种操作:

  • Union(Root1,Root2):把子集Root2并入到Root1中,要求Root1与Root2不相交,否则执行合并。
  • Find(x):搜索单元素x所在的集合,并返回该集合的名字。
  • UFSet(s):构造函数将并查集中s个元素初始化为s个只有一个单元素的子集合。

并查集中需要两种数据类型的参数:集合名称类型和集合元素的类型。在许多情况下,可以用整数作为集合名。如果集合中有n个元素,可以可以用1~n以内的整数来表示元素。实现并查集的一个典型方法是用树形结构来表示元素及其所属子集的关系。用这种方式,每个集合以一棵树表示,树的每一个结点代表集合的一个单元素。所有各个集合的全集构成一个森林,并用树和森林的父指针表示来实现。其下标代表元素名。第i个数组元素代表包含集合元素i的树结点。树的根结点的下标代表集合名,根节点的父为-1,表示集合中的元素个数。

并查集的合并与查找操作实现起来很简单,但性能特性并不好。假如最初n个元素自成一个单元素的集合(即S_{I}={I},0\leqslant I<n),相应的数结构为n棵数组成的森林,parent[i]=-1, 0\leq I <n。如果做如下处理:Union(n-2,n-2),......,Union(1,2),Union(0,1), 将产生如下图所示的退化的树。

为了避免产生退化树,一种改进的方法是,元素个数少的集合并入元素个数多的集合。先判断两个集合中元素的个数,如果以i为根的树中结点个数少于为j为根的树中结点个数,则让j成为i的父,否则,让i成为j的父。此即为Union(i,j)的加权规则。代码实现见函数UnionByNumbers(int Root1,int Root2)。

为了进一步改进树的性能,需要使用折叠规则来“压缩路径”,让子结点到达根节点的距离只有一步。折叠思路:如果j是从i到根的路径上的一个结点,并且parent[j]!=root[i],则把parent[j]置为root[i]. 代码实现见函数CompressPath(int i)。

//
//  main.cpp
//  并查集
//
//  Created by scnulpc on 2018/10/24.
//  Copyright © 2018年 scnulpc. All rights reserved.
//

#include <iostream>
class UFSet
{
private:
    int *parent;
    int size;
public:
    UFSet(int sz);
    ~UFSet();
    void Union(int root1,int root2);    //两个字集合并
    int Find(int x);                    //搜寻集合x的根
    void UnionByNumbers(int root1,int root2);//将root1,root2的中元素个数少的合并到元素个数多的
    void CompressPath(int i);            //从结点i向上逐次压缩
};
UFSet::UFSet(int sz)
{
    size = sz;
    parent = new int[size];
    for (int i=0; i<sz; i++) parent[i]=-1;
}
UFSet::~UFSet()
{
    delete []parent;
}
void UFSet::Union(int root1, int root2)     //合并两个集合
{
    if (Find(root1)==Find(root2)) return;
    parent[root1] += parent[root2];         //统统加到root1中
    parent[root2] = root1;
}
int UFSet::Find(int x)
{
    if (parent[x]<0) return x;
    else return Find(parent[x]);
}
void UFSet::UnionByNumbers(int root1, int root2)
{
    int num1 = Find(root1);
    int num2 = Find(root2);
    if (num1==num2&&num1!=-1) return;    //初始化都为-1
    if (parent[num1]<parent[num2]) {    //root1元素个数多于root2
        
        parent[num1]+=parent[num2];
        parent[num2] = num1;
    }
    else                                //root2元素个数多于或等于root1
    {
        parent[num2]+=parent[num1];
        parent[num1] = num2;
    }
    
}
void UFSet::CompressPath(int i)
{
    int j = Find(i);
    int temp;
    while (i!=j) {
        temp = parent[i];
        parent[i]=j;
        i=temp;
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值