关闭

并查集算法学习(转)

标签: 数据结构算法并查集
199人阅读 评论(0) 收藏 举报
分类:

我读的博客地址是这个:

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; //掌门来了
}

路径压缩

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:273262次
    • 积分:6721
    • 等级:
    • 排名:第3652名
    • 原创:397篇
    • 转载:1篇
    • 译文:1篇
    • 评论:107条
    最新评论