Background:
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
---- 摘自洛谷某dalao
基本问题:
1、将两个集合合并
2、询问两个元素是否在一个集合当中
基本原理: 每个集合用一颗数来表示(不一定是二叉树)。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。
问题1:如何判断树根? A:if(p[x] == x)
问题2:如何求x的集合编号? while(p[x] != x) x = p[x];
问题3:如何合并两个集合? p[x]是x的集合编号,p[y]是y的集合编号。p[p[x]] = p[y];
(这里也就是说 把其中一颗树的根节点接到另外一棵树的孩子节点上(x的祖宗节点的父亲节点等于y节点的祖宗节点), 这样就合成了一棵树了)
问题4:如何求一个元素它所在的集合的所有元素的个数(即数的总结点数??)
A: 其实,我们可以在一开始的时候 就令s[i] = 1, 因为刚开始,集合里 只有一个元素,那么集合大小必然为1, 后面要合并集合,我们再使 s[find(x)] += s[find(y)] (find(x) 为查找对应元素祖宗节点的函数,下面会介绍), 因为两个数合并,那么根据上面所述, 会有一棵树在另外一棵树的下面,这样的话,那这颗大树的元素个数就是两颗数的元素个数之和啦! 然后 我们只需要输出 s[find(x)] 就好了, x对应的祖宗(根节点)的那颗树的节点个数!
最后再讲一讲find函数:
我们对于find函数 使用了压缩路径的方法:
首先:并查集的单次查询理想复杂度应该是O(logn)的,但是如果有一个这样的数据,并查集的复杂度就是O(n),
为了避免这种情况,我们需对路径进行压缩。
即当我们经过找到祖先节点后,回溯的时候顺便将它的子孙节点都直接指向祖先,使以后的查找复杂度变回nlogn 其实大部分是 0(1).......
具体操作:
while(q[x] != x) q[x] = find(q[x]); return q[x];
finish~~~~