算法模板学习专栏之并查集(一)入门

博主欢迎转载,但请给出本文链接,我尊重你,你尊重我,谢谢~

http://www.cnblogs.com/chenxiwenruo/p/7723406.html

特别不喜欢那些随便转载别人的原创文章又不给出链接的

所以不准偷偷复制博主的博客噢~~

并查集(一)之入门

1.入门简介

举一个简单的例子吧,有那么n个人,有的人是属于斧头帮的,有的人是属于火影村的,有的人是来自M78星云的,有的人是属于复联的。然后么,有次终极宇宙大战爆发了,然后一伙人要打架。但是打架可不能随便打啊,自己人不能打自己人啊。但是呢,我只知道自己队友是谁啊,但队友的队友我不一定不认识啊,我咋知道他是不是自己队友啊。所以现在的问题就是,比如我只知道A和B、B和C为队友,我如何得知A和C也为朋友,即如何判定任意给定两个人是否属于一个队伍。

并查集就是用来解决诸如是否是同一类的问题,比如图的连通性也算是,若干个节点和边,如何快速地判定两节点是否连通,如何判定图中有多少个连通域。

并查集的大致算法思想就是,每个元素都有一个父节点,可以理解为同类里的头头。这样,通过比较两个元素的父节点是否为同一个头头,就可以判断它俩是否为同一类别。

2.结构体

先给出整个模板

//并查集
struct UF{
    int fa[maxn]; //fa[i]表示第i个节点的父亲节点
    int num[maxn]; //num[i]表示所有以i为父亲节点的元素个数,即属于同一类的元素个数
    //初始化
    void init(){
        for(int i=0;i<maxn;i++){
            fa[i]=i;
            num[i]=1;
        }
    }
    //查找节点u的父亲节点
    int find_root(int u){
        if(fa[u]!=u)
            fa[u]=find_root(fa[u]);
        return fa[u];
    }
    //将节点x和节点y所在的类合并
    void Union(int x,int y){
        int fx=find_root(x);
        int fy=find_root(y);
        if(fx!=fy){
            fa[fy]=fx;
            num[fx]+=num[fy];
        }
    }
}uf;

 

3.初始化

首先,定义结构体,在C++中我推荐用结构体而不是类,因为结构体很方便、很简洁。用数组fa来表示节点的父亲节点,即所属的类。num[i]表示以i为父亲节点所在类的元素个数。

最初,每个节点独自构成一类,因此fa[i]=i,num[i]=1。

 

4.并查集的查

查找某个节点x的父亲是通过uf.find_root(x),来因此判断节点x属于哪一类。这里为了优化采用了路径压缩算法。这里讲一下为什么要路径压缩,假如有这么个样例:第一行代表其父亲节点,第二行代表节点编号。

2 3 4 5 6 7 7

1 2 3 4 5 6 7

如果我uf.find_root(1),那么它会先找到1的父亲2,再接着找2的父亲3,直到找到6后,最终才找到祖先7。如果每次都这样做的话,那么时间耗费太大。因此,这里采用路径压缩算法,使得再查找的过程中,把所有找到的节点的父亲都改为7,也就是说,1~6的父亲节点fa[i]都设为7,这样下次再寻找任意一个1~6中的节点时,只需要O(1)的时间复杂度。

那么如何在查找的过程中同时进行路径压缩呢,这就采用了递归的思想。

//查找节点u的父亲节点
int find_root(int u){
    if(fa[u]!=u)
        fa[u]=find_root(fa[u]);
    return fa[u];
}

 

首先,如果fa[u]==u,那么显然,该节点是祖先节点,直接返回值即可。

如果不是的话,那么,就要对fa[u]进行更改,怎么更改呢?很明显啊,当然是把它当前父亲节点的父亲赋值给他,那么当前父亲节点的父亲节点怎么得来,同样是查找。

同样拿上面的例子解释,find_root会一直找下去,直到节点7,然后把7值返回,赋值fa[6],那么现在fa[6]=7,返回赋值给fa[5]=7,就这样1~5的父亲节点都是7。这就完成了路径压缩的过程。

递归是个好东西,好好看好好学~

5.并查集的并

知道如何查找一个节点的父亲节点后,那么接下来就是要进行合并了。那么如何将两个不相干的节点归为一起呢?很明显,将其中一个节点的父亲节点分配给另一个就是了。当然,先要判断这两个节点是否已经是同一类了,人嘛总会偷懒的,能少做就少做点,不是嘛。同时,这样做也保证了两类里面的节点都先路径压缩了一遍。

//将节点x和节点y所在的类合并
void Union(int x,int y){
    int fx=find_root(x);
    int fy=find_root(y);
    if(fx!=fy){
        fa[fy]=fx;
        num[fx]+=num[fy];
    }
}

 

 

6.用法

判断是否是同一类

这个很简单,给定要判定的两个节点,find_root()一下就可以了

判断有多少个类/是否连通

这个就基本上要遍历一下所有的节点,然后再把它们的父亲节点加入到set集合里,然后再统计一下个数即可。

7.练习题

并查集模板题

PAT甲题:1034,1107,1114,1118

求连通分支/判断连通性

PAT甲题:1013,1021,1126

题解请戳:http://www.cnblogs.com/chenxiwenruo/p/6102219.html

建议自己先A,实在A不出来,再看题解~~

posted @ 2017-10-24 14:36  辰曦~文若  阅读(438)  评论(0编辑  收藏  举报