数据结构——并查集

 

目录

一、基本形式与操作

基本形式

基本操作

二、使用方法及逻辑分析

三、模板代码

四、合并/查询优化

五、总结


        A和B是朋友,B和C是朋友,A和C也就是朋友(管他是不是朋友,俗话说四海之内皆兄弟)。有了这样一个前提,A、B、C同属于一个关系圈。

        那么如果给你好多对这种“朋友”关系,比方说x和y是朋友,n和z是朋友......问你和我是不是朋友?(当然是了我的铁子!)这时候就需要一个很优秀的数据结构为我们自动地划分好帮派,在查找的时候,只要我们同属一个帮派,那就能断定XX和XX是不是朋友关系。

        于是,并查集诞生了!!!

        所谓并查集,就是要维护一个集合,这个集合我们可以用开篇所说的“朋友圈”的关系来简单理解,也可以用一个从属关系来理解,比如A是武当派的,B是A的师弟,那B也应该划分到武当派这个集合中去。对应的例子和理解方式有很多很多中,这也就扩大了并查集的应用范围。

一、基本形式与操作:

基本形式:

        既然我们是为了给不同的事物分帮或者说将它们划分到不同的集合里去,那么一个集合结构是必须存在的,一般来说使用数组啊,hash表啊都可以表示这个关系。比如2是从属于3的(2是3的朋友等等),那么可以让s[ 2 ] = 3,至于为什么这样做,下面会有讲解。

基本操作:

  • Find:并查并查,一定有并有查,Find就是的操作。比如问你2和3是不是朋友,我们可以用Find查到2是属于哪个集合的,再查查3是属于哪个集合的,看着两个集合是不是同一个,就可以解决这个问题了。

  • Union:Union就是的操作。操作的功能体现在给你一对关系,能用Union把他们划分到同一个集合中。这就是Union要完成的工作。

二、使用方法及逻辑分析

        代表元的概念:有五个数1,2,3,4,5,他们最初是分属于五个不同的集合,即1∈[1],2∈[2],3∈[3],4∈[4],5∈[5],实际上我们并不需要真的把这五个集合的名字或者含义表达出来,我们只需要从这个集合的元素中找出一个元素作为这个集合的“领头人”。或者说将这个集合中的元素堪称一棵树,那个代表元就是这棵树的根。在查找一个元素的从属关系的时候,可以查找他的代表元是谁;在合并两个集合时,可以将两个代表元合并为一个共同的代表元。

        拿一个例子来讲比较具体:

        

        树状:

 

        有了代表元的概念,重新理清一下上面的关系,比方说现在,1的集合中只有1一个元素,那么集合一的代表元就是1,1就是集合一。

        现在给出几组操作及过程的图析:

        1.合并(1,2)

        合并完后可以是下面两种情况:

         区别就在与是想用2作代表元(上图)还是1作代表元(下图)。

        上图对应到数的形状是这样:

        下图同理不作演示。         

        2.合并(1,3)

        进行这部操作时,1已经被划入以2为代表元的集合,那么再合并时应找到1对应的代表元与3进行合并。也有以下两种情况:

         上图树形: 

         下图树形:

        这两个图就有区别了,区别在于树的高度不同,很显然树的高度越来越高,那么寻找代表元花费的时间相对也就越长,反之则越短。

        3.查操作就相当于一个找根过程,不再赘述。

三、模板代码

int Find(int x)
{
    if(x == s[x]) //x == s[x],找到根,返回
    {
        return s[x];
    }
    else //x != s[x]说明x不是代表元,递归找根
    {
        return Find(s[x]);
    }
}
void Union(int x, int y)
{
    //对代表元进行合并
    x = Find(x); //先找代表元
    y = Find(y);
    if(x != y) //代表元不同才需要合并
    {
        s[x] = s[y];
    }
}

Find的非递归版本可自行总结。

四、合并/查询优化

         前面已经提到,树的高度会影响查询速率,同时如果一棵树只沿着同一个方向延伸很有可能会退化成一个单链表。因此,我们可以在合并时将较矮的那棵树合并到较高的那棵树上,只需要再多维护根(代表元)所在树的高度,合并时对高度进行比较即可:

合并操作的优化:

/**
    h[]是代表树的高度的数组,如果两棵树高度
    相同,那么哪颗树都可以作为新的根。否则将
    矮的拼接到高的上面
*/
void Union(int x, int y)
{
    x = Find(x);
    y = Find(y);
    if(h[x] == h[y])
    {
        h[x]++; //合并后高度加一,自行脑补
        s[y] = s[x];
    }
    else 
    {
        if(h[x] < h[y])
        {
            s[x] = s[y];
        }
        else
        {
            s[y] = s[x];
        }
    }
}

        在查询时,我们要从叶子结点遍历到根节点,每一次重复做这个操作,如果树的高度累计了很高,实际上也是不利于查找效率的。一个比较聪明的做法就是在查询时,将当前结点的根统一设置为代表元,这样,每进行一次查询操作,都可以将一棵树的高度变为1。

 

 查询操作的优化:

int Find(int x)
{
    if(x != s[x])
    {
        s[x] = Find(s[x]); //置根操作
    }
    return s[x];
}

五、总结

        并查集对不相交的集合的合并及查询功能效率提高很明显,用树来理解并查集是最直观的方式之一。并查集的应用十分广泛,在对解决连通图、连通分量、生成树甚至贪心问题中的期限作业等等方面都有它的影子。熟悉并查集及操作方式会帮我们在处理相应范围内的问题有很大的帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落英S神剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值