高级数据结构2--并查集

本文是高级数据结构系列第2篇。

引入

您需要维护一个数据结构,支持:
1、把无重复元素的a,b所在的两集合合并为一个集合。
2、问a,b元素是否在同一个集合内。
其中,1与2号操作的次数总和不超过m(m<=5*1E6),a,b的大小不超过n(n<=1E6)

朴素算法
看似还要维护集合,很麻烦,但是……
我们可以使用图论模型来解这道题(虽然过不了)。

很显然,我们可以直接在图中每次对1操作用邻接表连边,对2操作用BFS判断是否在同一个联通块内(DFS会爆栈)。最坏情况下,复杂度会变为O(nm),是跑不过的。

介绍

既然图论这条路似乎走不通,那我们就在回到原来的问题。
先考虑最简单的问题:假设合并的只有a,b两个元素,如何解决?
合并a,b与合并b,a显然等价。
那么,我们就可以从a连一条指向b的边,如下图所示:

Created with Raphaël 2.1.0 a b

这给我们了一个启示:集合问题其实可以用树来解决,每个树的根(不包括它的子树的根)便可以代表这个集合,其中的元素即这棵树的所有元素。
例如,a向c连了一条边,b向c连了一条边,则a,b都属于c所代表的集合。
这样的话,合并集合就是将A集合的根变为B集合根的儿子。
查询在不在同一个集合中,只要分别求出所在树的根,判断是否一样就行了!

int ask(int x){
    return fa[x]==x?x:ask(fa[x]);
}//询问第i个节点所在的树的根
void merge(int a,int b){
    a=ask(a),b=ask(b);//分别找到自己所在集合的“代表”
    if (a!=b) fa[a]=b;
}
void query(int a,int b){
    a=ask(a),b=ask(b);
    if (a!=b) printf("no\n");
    else printf("yes\n");
}
int main(){
    for (int i=1;i<=n;i++) fa[i]=i;
    //最开始时,每个孩子都向自己指一条边
    for (int i=1;i<=m;i++){
        scanf("%d%d",&opt,&a,&b);
        if (opt==1) merge(a,b);
        else query(a,b);
    }
    return 0;
}

看似已经是很优越的算法了,但是只要是树,就有可能退化为链,这里也确实会,而且更可怕的是用ask()会爆栈。
于是我们有一种极为优异的方法:路径压缩。

路径压缩

在如上的ask函数中,我们想一下:如果a,b所在的树根一样,那他们肯定在同一个集合里。而且,如果在同一个集合中,他们的父亲是什么完全没有关系,只要他们的祖先一样就行了!
那我们就可以让同属一个集合的两个元素都成为根的孩子喽?这样树上这条链的高度就最多是2了。
然后把上文中的a,b换成“一个节点”和“这个节点的父亲”。于是,每次询问的时候,我们就可以把(一个节点与他的父亲)的父亲都设成根节点。

int ask(int x){
    return fa[x]==x?x:fa[x]=ask(fa[x]);//只多了这么一句话
}//询问第i个节点所在的树的根
void merge(int a,int b){
    a=ask(a),b=ask(b);//分别找到自己所在集合的“代表”
    if (a!=b) fa[a]=b;
}
void query(int a,int b){
    a=ask(a),b=ask(b);
    if (a!=b) printf("no\n");
    else printf("yes\n");
}
int main(){
    for (int i=1;i<=n;i++) fa[i]=i;
    //最开始时,每个孩子都向自己指一条边
    for (int i=1;i<=m;i++){
        scanf("%d%d",&opt,&a,&b);
        if (opt==1) merge(a,b);
        else query(a,b);
    }
    return 0;
}
P.S.:其实,还有一种按照集合内元素多少进行合并的更高效算法(即修改merge),但是一般上面这个就够了,而且我也不会写。

好题推荐:

最小生成树的kruskal算法。传送门
星球大战:维护和根之间的战舰数目。
团伙:自己的敌人和敌人是一对朋友。
食物链:维护和根之间的关系。
关押罪犯:类似kruskal。

猜你喜欢

转载自blog.csdn.net/qq_35505348/article/details/76599649
今日推荐