算法模板学习专栏之并查集(一)入门
博主欢迎转载,但请给出本文链接,我尊重你,你尊重我,谢谢~
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不出来,再看题解~~