并查集实例详解

来源:互联网 发布:卓智网络是国企 编辑:程序博客网 时间:2024/06/06 00:17

一、简介

并查集,是一种很有用的数据结构。在算法导论的第22章:用于不相交集合的数据讲的就是并查集,看算法导论确实是需要耐心和耐心的。大致过了一遍,这篇博文将结合自己的理解,举个生动的例子,并用代码实现,让学习并查集变得有趣。

多个不相交的数据集合可以把它想象为多棵独立的树。
并查集的常用基本操作有

  1. 查找任意两个树节点,看看它们是否属于同一棵树(其实就是查不同节点的根节点是否一样即可),如下图b & e 同属于根为c的树,所以它们在同一个集合;而h & g则不属于同一集合。
  2. 合并这些独立的树,成为一棵更庞大的树。(其实也很简单,就是将一颗树的根节点成为另一个树根节点之下的孩子即可)

合并操作如图所示:
这里写图片描述

1和2两步,也就是所谓的查与并。

二、数据结构描述

刚开始我听这高大上的数据结构名感觉应该会很难,又是树又是森林什么的。然而研究了一番发现并查集并不是想象中那么难,而且特别有意思。

举个例子,假设有A B两家公司。 (画图是两棵树,语言描述如下)
A:
*********6 ——–老板
******2****3 ——经理
******5****4 ——-员工 (5号员工的直接上司是2号经理,其余类似)

B:
********8 —– ——老板
*****1******7 ——经理 (7号经理手下无人)
*****9 —————-部长
*****0 —————员工

并查集的数据结构表示非常简单直观,用数组表示即可。
那么用一个数组就可以表示两家公司的等级构成:
int myboss[ 10 ] = { 9, 8, 6, 6, 3, 2, 6, 8, 8, 1 };
即0号员工的上司是9号,1号员工的上司是8号,2号员工的上司是6号……

这么简单的一个数组就表示了两棵树,一个森林,有点意思。

那么如何实现并 & 查便是并查集的关键之处。

三、并查集之并与查

1-查的两种实现

1)非递归方式

查老板,首先要明确一点就是,老板的老板就是自己!开公司就是自己给自己当老板,多霸气。
假如我们想查5号员工的老板是谁?应该怎么查呢?
思路很简单,先问5号的上司2号,问问他是不是老板,他说我不是老板,我只是普通的经理而已。然后2号经理说,我帮你向上级问问吧。于是2号经理打电话给自己的上司6号,问6号是不是老板。6号说废话,我就是你老板(因为6号没有上司了)。

通过逐层向上询问的方式,我们就可以查找到任意员工的最终老板是谁。(也就是从任意节点回溯到树的根节点)

如何用代码实现以上的思路呢?
只需以下这个简单的函数即可:

//不断向上级询问找某人的老板//老板的老板就是自己int find_boss(int person) {    int boss = person;  //先假设该员工就是老板    int leader,tmp;  //tmp用来保存某员工的直接上司    //没找到老板则一直循环    while ( myboss[boss] != boss )          boss = myboss[boss];    //*******路径优化******//    //每一个人的直接上司都变成老板(不再是之前的经理或部长了)    leader = person;      while (leader != boss) {        tmp = myboss[leader];        myboss[leader] = boss;  //上司直接变成老板        leader = tmp;    }    return boss;}

路径优化

上面的代码有一段是所谓的路径优化。什么意思呢?
其实很简单,就是原来的那颗三层的树,变得只剩下两层变成
如下形状:
******6 ——老板
2***3****4****5 —–经理

也就是说4号和5号员工都升职加薪,走向人生小巅峰了,老板成为了他们的直接上司(然而,他们手下并没有员工,光杆司令)。
而什么时候会发生路径优化呢?就是每查一次,并查集的层次结构就会改变一点。即,只有打电话问谁是老板的员工才能获得机会。
例如执行了 find_boss(5);
那么公司结构变为:
**********6 ————老板
******2**5***3 —-经理(5号升职了)
**************4 —-员工(4号仍然是3号的员工,因为他没有主动打电话给老板)

假如B公司的9号员工打给老板要求升职
那么公司结构则变为:
***********8
********1**7***9
****************0

2)递归方式

递归方式的查老板那就更加形象简单了,路径优化这一过程多省略了,因为在递归回溯的时候就完成了路径优化,升职加薪了。
代码:

//递归方式找老板,在递归回溯的过程中同时也实现了路径优化//即每个人的直接上司都变成老板了 r=recursiveint find_boss_r(int person) {    if( person != myboss[person])        myboss[person] = find_boss_r(myboss[person]);    return myboss[person];}

2.A,B公司合并

并查集另一个重要的操作就是并了。上述例子的A,B两家公司合并就是并查集的并操作。
那么如何合并呢?十分简单形象,加入是A公司收购了B公司,那么只需要让B公司的老板,变为A公司老板的直接下属就可以了。

具体实现代码:

//合并两家公司,即A公司老板成为了B公司老板的老板void join(int person_one, int person_two) {    int boss_a ,boss_b;     boss_a = find_boss(person_one);    boss_b = find_boss(person_two);    if (boss_a != boss_b)         myboss[boss_b] = boss_a;}

四、测试代码:

int main(int argc,char *argv[]){    int boss;    boss = find_boss(9);    printf("9号员工的老板是:%d \n",boss);    boss = find_boss_r(4);    printf("4号员工的老板是:%d \n",boss);    join(5,0);    boss = find_boss(7);    printf("A公司收购B公司后,7号的老板是: %d\n",boss);    return 0;}

这里写图片描述

五、并查集的应用

理解了并查集可以解决一大类相似的问题,不过具体如何解决还是得看个人功底了。
大家百度一下就有很多难题,在此就省略了。

0 0
原创粉丝点击