并查集
并查集也是用来维护集合的,和前面学习的 set 不同之处在于,并查集能很方便地同时维护很多集 合。如果用 set 来维护会非常的麻烦。并查集的核心思想是记录每个结点的父亲结点是哪个结点。然而可以知道,这样空间时间复杂度极高,无法通过题目的时限.
并查集基本
基本结构
- 初始化
初始化很简单,将每个点所在集合初始化为它自己就可以了。
void init() {
for (int i = 1; i <= n; ++i) {
fa[i] = i;
}
}
- 查找
这一步,我们只需要找到根节点,即元素所在的集合,然后继续判断。
int get(int x) {
if (fa[x] == x) {
// x 结点就是根结点
return x;
}
return get(fa[x]); // 返回父结点的根结点 }
- 合并
将两个不同元素所在的集合合并为一个集合。
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) { // 不在同一个集合
fa[y] = x;
}
}
实现并查集
首先,初始化
int fa[1000];
int n, m;
我们先把并查集的框架实现好,便于后面直接调用。初始化实际上就是把每个点的父亲结点赋值为自己。
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
我们继续实现get函数
int get(int x) {
if (fa[x] == x) {
return x;
}
return get(fa[x]);
}
接下里我们实现 merge 函数,合并两个结点到一个集合。
void merge(int x,int y) {
x=get(x);
y=get(y);
if (x!=y) {
fa[y]=x;
}
}
初始化要在 输入之后,这是平时写程序很容易错误的一个点。
cin >> n >> m;
init();
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
merge(a, b);
}
最后我们统计几何的个数。
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (fa[i] == i) {
cnt++;
}
}
cout << cnt << endl;
实现争取代码:
#include<bits/stdc++.h>
using namespace std;
int fa[10000];
int n,m;
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int get(int x) {
if (fa[x] == x) {
return x;
}
return get(fa[x]);
}
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
fa[y] = x;
}
}
int main() {
cin >> n >> m;
init();
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
merge(a, b);
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (fa[i] == i) {
cnt++;
}
}
cout << cnt << endl;
return 0;
}
路径压缩
并查集的时间复杂度很高,最坏可以达到O(n),所以,我们更适用于路劲压缩的并查集,这一回,就来给大家讲一讲路劲压缩后的并查集。
路径压缩 的思想是,我们只关心每个结点所在集合的根结点,而并不太关心树的真正的结构是怎么样 的。这样我们在一次查询的时候,可以直接把查询路径上的所有结点的 都赋值成为根结点。实现 这一步只需要在我们之前的查询函数上面进行很小的改动。