关于并查集的简单整理

前言

这篇整理主要是给自己做个记录,这里做参考的文章要好的多。(有涉及到树和图的知识)
参考自知乎《算法学习笔记(1) : 并查集》

一、什么是并查集,用在哪?

关于定义,引用百度百科的原文:
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。
这句话中提取出如下信息。

本质树型的数据结构
处理对象一些不相交集合
操作合并、查询

对这个算法的介绍,网上通常引用“找亲戚的例子”:有n个由若干个不同的家族组成的人,我们只知道所有人中父(母)与子(女)的关系,判断其中的A和B是否是有血缘关系。
我们可以顺着A的关系一步一步找,再顺着B的关系一步一步找。

1. 比如A的爸爸是C,C的爸爸是D,D没有爸爸,那么我们可以认为D是A的爷爷,并且D是这个家族中最大的一辈人,那我们就可以简单的称这个家族为D家族。
2. 同理,我们假设在所有人找到B的爸爸是X,并且X没有爸爸,我们简单的称B和X所在的家族是X家族。
3. 出于某种原因,我们并没有找到X和D的爸爸是谁,现实生活中我们无法判断X和D有没有血缘关系,但是在这里,X,D作为最顶层的一辈人,我们把他俩看作是根节点,根节点和根节点没有血缘关系。所以作为X子级的B和D子级的子级的A没有血缘关系。
4. 同理,若A和B最终的根节点指向同一个,那就说明他们必然是有血缘关系的。

二、并查集的原理

并查集的原理就是如“找亲戚”的例子一样,我们把其中的每个人都看作是一个独立的集合,当知道这个集合的根节点(一个人作为一个集合的时候,他自己就是这个根节点)和另一个集合的节点有关系时,我们就把这两个集合合并一个大集合,进行若干次合并之后,剩下的集合之间没有关系时,不再合并。
查询操作同理,分别查询两个子节点的根节点,如果两个子节点的根节点是同一个,说明他们存在某种联系。

三、并查集的几种简单写法

1.原理法

int fa[MAXN];
void init(int n)
{
    for (int i = 1; i <= n; ++i)
        fa[i] = i;//初始化节点,每个节点的父节点是他自己
}

int merge()//合并
{
//根据某种规则,建立节点之间的联系。
//如点1指向点2,则fa[1]=2;说明点1的父节点是点2.
}

int find(int x)
{
    if(fa[x] == x)//在合并操作之后,如果这个节点的父节点是他自己,说明这个点就是根节点。
        return x;
    else			//否则,继续查找x父节点的父节点是不是他本身。
        return find(fa[x]);
}

2.路径压缩法

本质我们是想找到两个节点之间的关系,在查找他们的根节点的时候需要顺着父节点一个一个遍历,如果能知道某个节点的根节点是哪个,并且直接让这个节点指向这个根节点,当所有的子节点只有根节点这一个父节点的时候,再次查询的时间复杂度就降为O(1).(可以看一下开头参考文章的图)

int find(int x)
{
    return x == fa[x] ? x : (fa[x] = find(fa[x]));
    //如果x的父节点是他本身,那么这个x的节点就是根节点
    //否则让x的父节点等于最后查到找的根节点
}

3.按秩合并法

原理法中,我们选择的合并规则是,谁是父节点谁就最大,把最大的作为根节点。
想想一下,如果一颗树的中间某层上的节点只有一个,那么我是不是可以把这个这个树从那一层“掰开”,让中间那个节点作为父节点,这样就可以降低整棵树的深度。
例如在下图我有一颗深度是6的树。
在这里插入图片描述
把它折半,⑤看成是根节点。
在这里插入图片描述
这样深度为6的树就变成深度为4的树了。
虽然改变了树的结构,但是这几个元素任然在一个集合中(一颗树上)。在上面的例子中,并不能影响判断两个人是不是有血缘关系,降低树的深度反而加快了算法的速度。
同理,当我们发现某个集合的根节点和另一个集合有关系时,需要适当选取根节点进行合并。

void init(int n)
{
    for (int i = 1; i <= n; ++i)
    {
        fa[i] = i;
        rank[i] = 1;
    }
}

void merge(int i, int j)
{
    int x = find(i), y = find(j);    //先找到两个根节点
    if (rank[x] <= rank[y])
        fa[x] = y;
    else
        fa[y] = x;
    if (rank[x] == rank[y] && x != y)
        rank[y]++;                   //如果深度相同且根节点不同,则新的根节点的深度+1
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值