不带权并查集的操作

来源:互联网 发布:索隆的三把刀最好淘宝 编辑:程序博客网 时间:2024/06/05 20:57

一、不带权并查集的操作:

1.找祖宗节点(树的根节点)体现在Find函数里。

2.连接两棵树,体现在unite函数里。

3.路径压缩,体现在Find函数里。

4.初始化什么乱七八糟的东西。

1、2、4操作是必要的,3是优化操作。


这里我习惯pre数组记录每个节点的根节点。

二、朴素做法:


1.找祖宗不加路径压缩的代码:

int Find(int x){    while(x!=pre[x]) x=pre[x];    return x;}

这个很朴素,只要这个节点的根不是自己,就一直往自己的父亲节点上找。

2.合并两棵树:

朴素的想法是,只要x和y有关系,那么我就把x所在的父节点变成y就是了。

void unite(int x,int y){    pre[x]=y;}

但是这个代码是错误的。其实这一共有两种情况。一种是x和y在同一棵树上,比如1和3在一棵树,同是2和3在一棵树,又给你个条件是1和2在一棵树上,当x和y在用一棵树上式,这个代码还算正确。第二种情况是x和y不在同一颗书上,这个代码就是错误的了,因为除了要完成把x和y联系到一起这个任务之外,我们还要维护树的性质。pre数组记录了这个点的父节点,如果仅仅是pre[x]=y的话会破坏被合并的树父节点和子节点的关系(子节点和父节点的关系就变了)。那么我们要怎么做呢?让他们的根连接根(让x树的根连到y树的根),这样就避免了破坏树的性质了。

代码如下:

void unite(int x,int y){    int fx=Find(x);    int fy=Find(y);    if(fx!=fy)    {        pre[fx]=fy;    }}

这个代码的意思是,先找到x所在的树的根,再找到y所在的树的根,如果两个根节点不一样(两个点不在同一棵树上),那么就把两棵树的根节点联系在一起,如果两个点本身就在一棵树上,就不做操作(你们本来就在一棵树上,本来就已经联系起来了,那还再次连什么连……)。


到此,Find函数和unite函数就已经能够实际应用了,就是基本操作的朴素版。


三、优化

其实这里的习惯优化只有一个,那就是大招——路径压缩。(其实还有启发式合并)

举个例子:

以1为根的树有两个叶子,标号2,3;以4为根的树有两个叶子,标号为5,6。(最好自己画个图哦)

当我们用上述的unite函数连接3、5这两个点之后,会有这样的树:

4是根节点,有三个子节点分别是1、5、6。1这个子节点还有两个子节点分别是2、3。

这样下次找2的根节点时候,要先找到1这个父节点,才能找到4这个祖宗。

因为并查集强调的不是树的结构而是集合,只要在一棵树上的元素,就是在一个集合里。一般解决pre数组这个森林里有多少棵树这个问题。

那我让2和3节点直接认4这个祖宗做父节点就是了,反正不会对最终的结果造成影响,还能快速的找到点的祖宗。

怎么做呢?

在Find函数里做手脚:

int Find(int x){    if(x==pre[x]) return pre[x];    else pre[x]=Find(pre[x]);}


这里采用了递归的方法。(还有一种写法是循环的,不过个人不习惯用那个)Find函数最终会带着根的序号返回,赋给x所有的先辈的节点,让它们直接认祖宗为父亲,也就压缩了路径。

注意:路径压缩不是在合并的时候压缩的,而是在找根节点的时候压缩的。

阅读全文
0 0