并查集入门(一)

并查集从名字中就能知道两种操作,合并和查找。它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。并查集其实本质上就是树形结构,如图

如何存储这个结构呢?用一个数组f存储, 例如h的双亲节点是d即 f[ h ] = d,  f[ d ] = b, f[ b ] = a;f[ a ] = a;就是这种存储结构。 

并查集主要的操作合并和查找,但是在合并和查找之前,还需要做初始化操作,如图

for(int i  = 1; i <= n; i++)f[i] = i;

为什么这么初始化稍后讲解。

然后是查找操作,如何进行查找呢?

int Find(int x){
    while(x != f[x])
    x = f[x];
 return x;
}

查找操作其实就像遍历一样从数的x这个位置遍历到根节点就结束查询。如图

例如我想从x = 4开始查询,f[ 4 ]存储的是5, 所以 4 != f[ 4 ] , 即x != f[ x ]; f[ 5 ]存储的是3, 所以 5!= f[ 3 ] , 即x != f[ x ];f[ 3 ]存储的是1, 所以 3 != f[ 3 ] , 即x != f[ x ];f[ 1 ]存储的是1, 所以 1 != f[ 1 ] , 即x = f[ x ];然后返回x.上述过程就是查询的过程,子节点存的是双亲结点,直到查询到根节点结束查询。

如何进行合并操作呢?就是查找x, y的根节点是否是一个,如果是一个那就不需要合并了,因为他们就是在一棵树上,如何根节点不是一个,那就把他们合并, 把一个的根节点fx指向另一根节点fy, 此时fx就不是根节点,只有fy是根节点。

void Join(int x, int y){
    int fx = Find(x), fy = Find(y);
    if(fx != fy)
        f[fx] = fy;
}

这个就是合并操作了,又回到最开始的问题,初始化的作用,初始化就是确定根节点,如果f[ x ] == x, x就是根节点,否则无法找到根节点。

 虽然并查集的三个骚操作都已经大概知道了,但是你有没有想过,如果不断合并结果就是一条线,查找时候就灰常的麻烦,所有就有了压缩路径,就比如上图的4, 5,在查找的时候都是查找到根部,那么有的路径其实可以压缩,所以4就不惜的经过3和5,直接指向1,同样的你4都不惜的我5也不惜的经过3了,所以3直接指向1了,所以所有数的双亲结点都是1,这么就做到压缩路径了。

int Find(int x){
    int p = x, tmp;
    while(x != f[x])
        x = f[x];
    while(p != x){
        tmp = f[p];
        f[p] = x;
        p = tmp;
    }
    return x;
}

How Many Tables   HDU   1213

链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213

题意:互相认识的人坐在同一个桌子,有几组互相认识的人就分成几组,只有在某一组有一个认识的人,那么你就跟整组相互认识。

题解:本题就是并查集的典型例题,只要查找有几个根节点就可以了,一个根节点就是一组相互认识的人。

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
#define N 1005
int f[N];
int Find(int x){
    int p = x, tmp;
    while(x != f[x])
        x = f[x];
    while(p != x){
        tmp = f[p];
        f[p] = x;
        p = tmp;
    }
    return x;
}
void Join(int x, int y){
    int fx = Find(x), fy = Find(y);
    if(fx != fy)
        f[fx] = fy;
}
int main(){
    int T, n, m;
    scanf("%d", &T);
    while(T--){
        scanf("%d %d", &n, &m);
        int fx, fy;
        for(int i  = 1; i <= n; i++)f[i] = i;
        while(m--){
            scanf("%d %d", &fx, &fy);
            Join(fx, fy);
        }
        int sum = 0;
        for(int i = 1; i <= n; i++){
            if(f[i] == i)sum++;
        }
        printf("%d\n", sum);
    }
return 0;
}

The Suspects      POJ   1611

题意:统计跟0有关的数字,只要跟0在一个树中那么这个数就跟0,有关系,此外一个数跟0有关的数有关,那么跟0也有关系。

题解:把所有的数据都正在存在f数组中,然后查找根和0的跟是一样数的个数,就是跟0有关系的数即被感染。

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define N 30005
int f[N];
int Find(int x){
    int p = x, tmp;
    while(x != f[x])
        x = f[x];
    while(p != x){
        tmp = f[p];
        f[p] = x;
        p = tmp;
    }
    return x;
}
void Join(int x, int y){
     int fx = Find(x), fy = Find(y);
     if(fx != fy)
        f[fx] = fy;
}
int main(){
    int n, m;
    while(~scanf("%d %d", &n, &m)){
        if(n == 0 && m == 0)break;
        for(int i = 0; i <= n; i++)f[i] = i;
        int k, t, a;
        while(m-- ){
            scanf("%d %d", &k, &t);
            for(int i = 1; i < k; i++){
                cin>>a;
                Join(t, a);
                t = a;
            }
        }
        int pos = Find(0);
        int sum = 0;
        for(int i = 0; i < n; i++){
            if(Find(i) == pos)sum++;
        }
        printf("%d\n", sum);
    }


}

P1111 修复公路   洛谷    1111

链接:https://www.luogu.org/problemnew/show/P1111

题意:同时开始修路,告诉村庄x, y间的路,多少天能修好,x和y之间可以通过别的地方到达,问最短多少天任意地点都可相互到达。

题解:此题也是并查集的题,但是在数据处理的时候有些不同,先把时间按照从小到大的顺序,然后在不断的合并,如能合并记录合并多少次,一次合并就会产生一条路num,同时更新此时时间的最大值。如果路num > n-1那么任意路之间可以相互到达。输出此时时间的最大时。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1005
#define maxn 10000000
//此题思路就是并查集算法,如果这两个数不是一个祖先,那么就合并
//但是此过程中先把a排序,为什么排序呢,因为排完序之后合并的时间应该是从最短的开始
int f[N];
struct Node{
       int x, y, z;
}a[100005];
bool cmp(Node A, Node B){
     return A.z < B.z;
}
int Find(int x){
    int p = x, tmp;
    while(x != f[x])
        x = f[x];
    while(p != x){
        tmp = f[p];
        f[p] = x;
        p = tmp;
    }
    return x;
}
int main(){
    int n, m;
    cin>>n>>m;
    for(int i = 1; i <= n; i++)f[i] = i;
    for(int i = 1; i <= m; i++){
        cin>>a[i].x>>a[i].y>>a[i].z;
    }
    sort(a+1, a+m+1, cmp);
    int cn = 0, ans = -1;
    for(int i = 1; i <= m; i++){
        int fx = Find(a[i].x), fy = Find(a[i].y);
        if(fx != fy){
            f[fx] = fy;
            cn++;
            ans = max(ans, a[i].z);
        }
    }
    if(cn >= n-1)cout<<ans<<endl;
    else cout<<-1<<endl;
return 0;
}

畅通工程   HDU   1232

链接:http://acm.hdu.edu.cn/showproblem.php?pid=1232

题意:为了使所有的地方都可以相互到达,还需要修多少条路。

题解:其实可以转换成有多少组关系,如果有n组,没组之间没有关系,那么就需要在建立n-1个关系。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<iostream>
using namespace std;
#define N 500004
int f[N];
int Find(int x){
    int p = x, tmp;
    while(f[x] != x){
        x = f[x];
    }
    while(p != x){
        tmp = f[p];
        f[p] = x;
        p = tmp;
    }
    return x;
}
void Join(int x, int y){
     int fx = Find(x), fy = Find(y);
     if(fx != fy)
        f[fx] = fy;
}
int main(){
    int n, m;
    while(~scanf("%d", &n)){
        if(n == 0)break;
        scanf("%d", &m);
        for(int i = 1; i <= n; i++){
            f[i] = i;
        }
        int fx, fy;
        for(int i = 1; i <= m; i++){
            scanf("%d %d", &fx, &fy);
            Join(fx, fy);
        }
        int sum = 0;
        for(int i = 1; i <= n; i++){
            if(f[i] == i)sum++;
        }
        printf("%d\n", --sum);
    }
return 0;
}

Corporative Network       UVALIVE   3027

链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1028

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
using namespace  std;
#define N 20005
int f[N], d[N];
int Find(int x)
{
    if(f[x] != x)
    {
        int root = Find(f[x]);
        d[x] += d[f[x]];
        return f[x] = root;
    }
    return x;
}
int main()
{
    int T, n, u, v;
    char a;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
        {
            f[i] = i;
            d[i] = 0;
        }
        while(scanf("%c", &a) && a != 'O')
        {
            if(a == 'E')
            {
                scanf("%d", &u);
                Find(u);
                printf("%d\n", d[u]);
            }
            if(a == 'I')
            {
                scanf("%d %d", &u, &v);
                f[u] = v;
                d[u] = abs(u-v)%1000;

            }
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值