并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
主要操作
合并两个不相交集合需要注意的是,一开始我们假设元素都是分别属于一个独立的集合里的。
操作很简单:先设置一个数组Father[x],表示x的“父亲”的编号。 那么,合并两个不相交集合的方法就是,找到其中一个集合最父亲的父亲(也就是最久远的祖先),将另外一个集合的最久远的祖先的父亲指向它。
C语言代码表示形式:
void Union(int x,int y) { fx = getfather(x); fy = getfather(y); if(fy!=fx) father[fx]=fy; }
判断两个元素是否属于同一集合
仍然使用上面的数组。则本操作即可转换为寻找两个元素的最久远祖先是否相同。可以采用递归实现。
C代码:
bool same(int x,int y) { return getfather(x)==getfather(y); } /*返回true 表示相同根结点,返回false不相同*/
路径压缩
并查集的优化
刚才我们说过,寻找祖先时采用递归,但是一旦元素一多起来,或退化成一条链,每次GetFather都将会使用O(n)的复杂度,这显然不是我们想要的。
对此,我们必须要进行路径压缩,即我们找到最久远的祖先时“顺便”把它的子孙直接连接到它上面。这就是路径压缩了。使用路径压缩的代码如下,时间复杂度基本可以认为是常数的。
int getfather(int v) { if (father[v]==v) return v; else { father[v]=getfather(father[v]);//路径压缩 return father[v]; } }
Rank合并
合并时将元素所在深度低的集合合并到元素所在深度高的集合。
C语言代码: 合并时将元素所在深度低的集合合并到元素所在深度深的集合。
void judge(int x ,int y) { fx = getfather(x); fy = getfather(y); if (rank[fx]>rank[fy]) father[fy] = fx; else { father[fx] = fy; if(rank[fx]==rank[fy]) inc(rank[fy]); } }
初始化:
memset(rank,0,sizeof(rank));
时间复杂度
O(n*α(n))
其中α(x),对于x=宇宙中原子数之和,α(x)不大于4
事实上,路经压缩后的并查集的复杂度是一个很小的常数。