并查集简单介绍

部分转载了洛谷上的题解:https://www.luogu.org/problemnew/solution/P1111

以及其他网站的图片及文字:https://www.cnblogs.com/MrSaver/p/9607552.html

  • 并查集

作用:可以查询两个节点是否是在同一个集合内

存储结构:树形结构,但一般用一位数组就能够存下来。每一个数组的值a[i] = b 指的是结点i的父节点是结点b。存储就是这样简单。

目的:查找结点x的祖先结点是谁

示意图:其实就是数组的元素转移,有点类似于链表的遍历

递归形式

int findfather(int x){
    return x == a[x]?x:find(a[x]);
}

循环形式

int findfather(int x){
    while(x!=a[x])
        x = a[x];
    return x; 
}
  • 集合的并

并就是讲两个节点合并到一个集合里面(这个集合必须是树),每个节点对应一个祖先,最老公共祖先的祖先就是自己,而每个节点在合并前的初始值也是自己,也就是:若有一个节点aa,设它的祖先为s[a]s[a],那么它的初始值就是s[a]=as[a]=a。这个合并操作并不难,只要判断一下这两个节点是否有祖先,没有就很好办,直接随便连,比如:aa和bb,他们都没有祖先(也就是祖先是自己),那么就可以s[a]=bs[a]=b了,如果s[a] \neq as[a]a,那么就让bb的最老祖先(可能是自己)再往上多一个祖先s[a]s[a],此时bb的最老祖先也就是aa的最老祖先了(不一定是aa)

 将结点x所在的集合与y所在的集合合并起来

void join(int x,int y){
    int fx = find(x),fy = find(y); //去找公共祖先 
    if(fx!=fy){
        a[fx] = fy;
    }
}
  • 压缩路径

对于查操作,每次寻找祖先都需要遍历一遍路径,类似与像链表一样遍历数组,而我们的目的仅仅想只要它的祖先是谁,仅仅是查祖先,就和路径没多大关系。我们知道,一条路径上的结点的祖先都是同一个,只需要让这些点都指向祖先就行了。

  • 具体代码

递归形式

int findfather(int x){
    if(x!=a[x])
        a[x] = findfather(a[x]);
    return a[x];
}

循环形式

int findfather(int x){
    int r = x;
    while(a[r] != r){ //找祖先 
        r = a[r];
    }
    int rr = x;
    while(a[rr] != rr){ //把路径的节点都指向祖先 
        rr = a[rr];
        a[rr] = r;
    }
    return r;
}
原文地址:https://www.cnblogs.com/bigbrox/p/11311070.html