您的位置:首页 > 理论基础 > 数据结构算法

并查集算法学习(转)

2016-08-30 19:11 253 查看
我读的博客地址是这个:

http://blog.csdn.net/dellaserss/article/details/7724401/

但是忍不住自己再写一遍顺便加入一些自己的思考。

这是我见过的最好的并查集教程,作者不知道是谁,但是读一遍,基本上并查集这个概念,以及如何快速写出代码来,基本没有问题了!

并查集是一种用来管理元素分组情况的数据结构。

并查集的结构:

每个组对应一棵树。每个元素对应一个结点。

而树的形状,结点之间谁是父亲等都不重要。

并查集是维护属于同一组的数据结构。

构成

并查集由一个整数型的数组和两个函数构成。

数组pre[ ]记录了每个点的前导点事什么,函数find是查找,join是合并。

int pre[1000];//记录每个点的前导点是什么
int find(int x) //查找根节点
{
int r = x;
while(pre[r] != r) //返回根节点r
r = pre[r];

int i = x,j;//路径压缩
while(i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void join(int x, int y)
{
//判断x,y是否连通,如果连通就啥也不做,否则就把这个两个分支合并起来
int fx = find(x);
int fy = find(y);
if(fx != fy)
pre[fx] = fy;
}


以上是官面儿的解释,下面是重点。

解释find函数,这个过程实在太欢乐了。

话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。



int pre[1000]数组记录了每个大侠的上级,大侠们从1或0开始编号,pre[15] = 3就表示15号大侠的上级是3号大侠。如果一个人的上级就是他自己,说明他就是掌门人了,查询到此为止。也有孤家寡人自成一派的。每个人只认识自己的上级,要想知道掌门是谁,只能一级一级查上去,find函数就是找掌门人用的

int find(int x) //查找掌门人
{
int r = x; //委托r去找掌门
while(pre[r] != r) //如果r的上级不是r自己
r = pre[r]; //r就接着找他的上级,直到找到掌门为止
return r; //掌门来了
}


join函数的含义也很有趣:

join函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个板块的所有点就都可以互通了。这在图上很好办,画条线就行了。但我们现在是用并查集来描述武林中的状况的,一共只有一个pre[]数组,该如何实现呢? 还是举江湖的例子,假设现在武林中的形势如图所示。虚竹小和尚与周芷若MM是我非常喜欢的两个人物,他们的终极boss分别是玄慈方丈和灭绝师太,那明显就是两个阵营了。我不希望他们互相打架,就对他俩说:“你们两位拉拉勾,做好朋友吧。”他们看在我的面子上,同意了。这一同意可非同小可,整个少林和峨眉派的人就不能打架了。这么重大的变化,可如何实现呀,要改动多少地方?其实非常简单,我对玄慈方丈说:“大师,麻烦你把你的上级改为灭绝师太吧。这样一来,两派原先的所有人员的终极boss都是师太,那还打个球啊!反正我们关心的只是连通性,门派内部的结构不要紧的。”玄慈一听肯定火大了:“我靠,凭什么是我变成她手下呀,怎么不反过来?我抗议!”抗议无效,上天安排的,最大。反正谁加入谁效果是一样的,我就随手指定了一个。这段函数的意思很明白了吧?

void join(int x, int y) //我想让虚竹和周芷若做朋友
{
int fx = find(x); //虚竹的老大是玄慈
int fy = find(y); //芷若mm的老大是灭绝
if(fx != fy) //玄慈和方丈不是同一个人
pre[fx] = fy; //委屈方丈当了师太的手下
}


考察路径压缩算法:

int find(int x) //查找x的掌门人
{
int r = x; //委托r去找掌门
while(pre[r] != r) //如果r的上级不是r自己
r = pre[r]; //r就接着找他的上级,直到找到掌门为止

//路径压缩:想把x和x的上级,x的上上级直到掌门的首座全都变成掌门的直系弟子
int i = x,j; //i初始代指x
while(i != r) //如果i不是掌门
{
j = pre[i]; //找到i的上级,并赋值给j
pre[i] = r;//i的老大现在改为掌门人
i = j;//再往上搞j,i曾经的老大,直到搞到掌门人自己为止
}
return r; //掌门来了
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息