并查集的实现

并查集的实现

题目描述

给定一个没有重复值的整形数组arr,初始时认为arr中每一个数各自都是一个单独的集合。请设计一种叫UnionFind的结构,并提供以下两个操作。

  1. boolean isSameSet(int a, int b): 查询a和b这两个数是否属于一个集合
  2. void union(int a, int b): 把a所在的集合与b所在的集合合并在一起,原本两个集合各自的元素以后都算作同一个集合

[要求]

如果调用isSameSet和union的总次数逼近或超过O(N),请做到单次调用isSameSet或union方法的平均时间复杂度为O(1)

输入描述:

第一行两个整数N, M。分别表示数组大小、操作次数
接下来M行,每行有一个整数opt
若opt = 1,后面有两个数x, y,表示查询(x, y)这两个数是否属于同一个集合
若opt = 2,后面有两个数x, y,表示把x, y所在的集合合并在一起

输出描述:

对于每个opt = 1的操作,若为真则输出"Yes",否则输出"No"

示例1
输入
4 5
1 1 2
2 2 3
2 1 3
1 1 1
1 2 3
输出
No
Yes
Yes
说明
每次2操作后的集合为
({1}, {2}, {3}, {4})
({1}, {2, 3}, {4})
({1, 2, 3}, {4})
备注:

1 ⩽ N , M ⩽ 1 0 6 1 \leqslant N,M \leqslant 10^6 1N,M106
保证 1 ⩽ x , y ⩽ N 1 \leqslant x, y \leqslant N 1x,yN


题解:

此题就是考察并查集的实现。并查集一般用来解决以下两个问题:

  • 合并两个集合
  • 查询两个元素是否在同一个集合

具体做法是:

  1. 把每个集合看做一棵树,开始时每个点都是一个集合,该集合的根节点是本身,即 fa[x] = x;

  2. 在查询某个点所在集合的根节点时,可以使用如下方法:return fa[x] == x ? x : find( fa[x] );

  3. 在合并 a 和 b 两个元素所在的集合时,将一个元素所在集合的根节点当做另一个集合根节点的子节点,即:

    void union( int a, int b ) {
        int fa = find( a );
        int fb = find( b );
        if ( fa == fb ) return;
        fa[fb] = fa;
    }
    

但是,上面的做法有一个缺陷:每次查询一个元素所在集合的根节点,都是从底往上查询,如果这棵树很深,时间复杂度很高。

由于我们希望每个元素到根节点的路径尽可能的端,最好只需要一步。于是出现了路径压缩算法:我们可以在查询过程中,把沿途每个节点的父节点都设为根节点 即可,于是新的查询算法可以改写成:

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

还有一种优化算法:按秩合并

该优化的思想是:把浅的树接在深的树后面,避免树越来越深,不过需要额外的辅助空间,并且优化不是很明显,一般 路径压缩 就够了。

代码:
#include <cstdio>
#include <vector>

using namespace std;

const int N = 1000000;

class UnionFind {
public:
    UnionFind( int size ) : max_size( size ) { }
    void init() {
        for ( int i = 0; i < max_size; ++i ) parent[i] = i;
    }
    int find( int x ) {
        return parent[x] == x ? x : (parent[x] = find( parent[x] ));
    }
    bool isSameSet( int a, int b ) {
        int pa = find( a );
        int pb = find( b );
        return pa == pb;
    }
    void _union( int a, int b ) {
        int pa = find( a );
        int pb = find( b );
        if ( pa == pb ) return;
        parent[pb] = pa;
    }
private:
    int max_size;
    int parent[N];
};

int main(void) {
    int n, m;
    int opt, x, y;
    scanf("%d%d", &n, &m);
    UnionFind unf( n );
    unf.init();
    while ( m-- ) {
        scanf("%d%d%d", &opt, &x, &y);
        --x, --y;
        if ( opt == 1 ) {
            puts( unf.isSameSet( x, y ) ? "Yes" : "No" );
        } else {
            unf._union( x, y );
        }
    }
    return 0;
}

推荐一篇教程:并查集

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值