并查集的实现
题目描述
给定一个没有重复值的整形数组arr,初始时认为arr中每一个数各自都是一个单独的集合。请设计一种叫UnionFind的结构,并提供以下两个操作。
- boolean isSameSet(int a, int b): 查询a和b这两个数是否属于一个集合
- 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
1⩽N,M⩽106
保证
1
⩽
x
,
y
⩽
N
1 \leqslant x, y \leqslant N
1⩽x,y⩽N
题解:
此题就是考察并查集的实现。并查集一般用来解决以下两个问题:
- 合并两个集合
- 查询两个元素是否在同一个集合
具体做法是:
-
把每个集合看做一棵树,开始时每个点都是一个集合,该集合的根节点是本身,即
fa[x] = x;
-
在查询某个点所在集合的根节点时,可以使用如下方法:
return fa[x] == x ? x : find( fa[x] );
-
在合并 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;
}
推荐一篇教程:并查集