您的位置:首页 > 其它

并查集实例详解

2015-08-04 00:13 337 查看

一、简介

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

多个不相交的数据集合可以把它想象为多棵独立的树。

并查集的常用基本操作有

查找任意两个树节点,看看它们是否属于同一棵树(其实就是查不同节点的根节点是否一样即可),如下图b & e 同属于根为c的树,所以它们在同一个集合;而h & g则不属于同一集合。

合并这些独立的树,成为一棵更庞大的树。(其实也很简单,就是将一颗树的根节点成为另一个树根节点之下的孩子即可)

合并操作如图所示:



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=recursive
int 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;
}




五、并查集的应用

理解了并查集可以解决一大类相似的问题,不过具体如何解决还是得看个人功底了。

大家百度一下就有很多难题,在此就省略了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: