并查集

并查集


并查集是这样一种数据结构,它提供查询和合并的功能,所以叫并查集对吧。 具体什么意思呢? 可以把并查集抽象成很多个互不相交的集合组成的集合。
那么查询指的就是选择两个元素,查询它们是否在同一个子集合里面。我们举个栗子,({}表示集合),{{1,3},{2,4}} 假设我们查询操作是find(int x1,int x2),那么find(1,2)find(1,3)分别返回假和真,因为很明显,1和3在同一个集合中,而1和2在不同的集合中。
合并呢?就是让两个元素所属的集合合并为一个集合,就拿上面那个栗子来说吧,假设我们的合并操作是unite(int x,int y),那么unite(1,2)的结果就是让原本的大集合变成了{{1,3,2,4}},两个集合变成了一个集合。


接着我们来思考一下怎么用代码实现并查集,不妨先试一试最正常的数组,如下:

i12345
set[i]12312

我们规定:set[i]表示所处集合,i表示该集合包含元素,照此,该例中有三个集合,1集合为{1,4},2集合为{2,5},3集合为{3}.
不难理解,查询操作的代码则可以如此编写:

int find(int x)
{
    return set[x];//set[i] 代表 i所处的集合,返回x所处的集合
}
int same(int x,int y)
{
    return find(x) == find(y);
}

合并操作的代码如下:

void unite(int x,int y,int n)//n代表一共有多少个元素
{
    int i;
    for(i = 0;i < n;i++){
        if(set[i] == y)
            set[i] = find(x);    //把y所处的集合名改成x的达到合并的目的
    }
}

但是,使用线性结构的数组是否能满足高效的特点呢?显然不能,于是,我们又想到了使用树结构,这时我们规定:
- i != set[i]时,set[i]是i的父亲
- i == set[i]时,i就是第i集合的根节点

再如下表:

i12345
set[i]12312

就可以看成三个集合,分别为{1->4},{2->5},{3}.
那么根节点就是区分不同集合的关键字,显然,获取所处集合的方法就是从元素出发,向上找到根节点,而合并就是把两棵树变成一棵树,为了更加高效,我们规定一种优化(路径压缩)的原则:
- 深度小的树连在深度大的树根节点上
- 让元素直接连在根节点上

代码如下(分析在注释中):

int set[MAX];//代表父亲的集合
int depth[MAX];//代表树的深度

int find(int x) //获取根节点值
{
    if(x == set[x])
        return x;
    else
        return set[x] = find(set[x]);//实现原则2(让元素直接与根节点相连)、同时沿着父亲向根节点递归进发
}

void unite(int x,int y) //合并x、y所处的集合
{
    x = find(x);y = find(y);   //找到x、y所处的集合
    if(x == y) return ; //如果是相同集合,则不需要合并,直接返回
    if(depth(x) < depth(y)) //如果x比y深度小,把x接到y上面,直接让y成为x的父亲即可
    {
        set[x] = y;
    }
    else
    {
        set[y] = x; //反之,把y接到x上
        if(depth(x) == depth(y)) depth(x)++;//如果两棵树深度相同,则合并之后它们的深度加一
    }
}

int same(int x,int y) //判断x、y是否在同一个集合中
{
    return find(x) == find(y);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值