并查集
并查集
基本操作
1.初始化
每一个元素初始时都是独立的一个集合
for(int i = 1; i <= maxn; i ++)
f[i] = i;
2.查找
递归查找的过程,直到找到一个元素值等于其集合的值即其根节点的集
int find_set(int x){
return x == f[x] ? x : find_set(f[x]);
}
3.合并
合并两个集合,通过查找操作找到其集的值,若不相等则修改其中一个使两个集的值指向同一个根节点的0值
void union_set(int x, int y){
x = find_set(x);
y = find_set(y);
if(x != y) s[x] = s[y];
}
4.统计集合数量
即统计一个根节点的数量,直接阐述较为抽象故引例
题目大意
认识的人必须在一个桌子上,给予T个测试样例,每个测试样例由两个整数N和M开始,N个人由1~N标记,M行每行由两个整数组成,意味着A和B彼此认识。求要多少张桌子。
偷偷BB,就是求有多少个
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 1010;
int f[N];
int T, n, m, x, y;
// 初始化
void init_set(){
for(int i = 1; i <= n; i ++) f[i] = i;
}
int find_set(int x){
return x == f[x] ? x : find_set(f[x]);
}
void union_set(int x, int y){
x = find_set(x);
y = find_set(y);
if(x != y) f[x] = f[y];
}
int main()
{
scanf("%d", &T);
while(T --){
scanf("%d%d", &n, &m);
init_set();
for(int i = 1; i <= m; i ++){
scanf("%d%d", &x,&y);
union_set(x, y);
}
int ans = 0;
for(int i = 1; i <= n; i ++){
if(f[i] == i) ans ++; // 查找未合并的根节点并且计入答案
}
printf("%d\n", ans);
}
return 0;
}
优化
1.合并的优化
优化原理:找到两个根节点,根据其高度,总使高度较小的集合合并到较大的集合中去,最终减少树的高度
int height[maxn + 1]; // 初始化时用height来定义元素i的高度,并在初始化时修改
void init_set(){
for(int i = 1; i <= maxn; i ++){
f[i] = i;
height[i] = 0; // 初始时树的高度为零
}
}
void union_set(int x, int y){ // 优化版合并操作
x = find_set(x);
y = find_set(y);
if (height[x] == height[y]) {
height[x] = height[x] + 1; // 合并,树的高度加一
f[y] = x; // 相等的话就随便了
}
else{ // 把矮树并到高树上,高树的高度保持不变
if(height[x] < height[y]) f[x] = y;
else f[y] = x;
}
}
2.查询的优化(路径压缩)
优化原理:这个优化的原理很简单,如果每次找父节点都要按照原路径找一遍显然太鸡儿蠢了。
在递归返回时,沿路把路径上的点都直接指向父节点,下次就可以在O(1)的时间内得到结果
上代码!!!
// 递归版本
int find_set(int x){
if(x != f[x]) f[x] = find_set(f[x]); // 路径压缩
return f[x];
}
// 非递归版本
int find_set(x){
int t = x;
while(f[t] != t) t = f[t]; // 此时t为根节点
int i = x, j;
while(i != t){
j = f[i]; // 用变量j记录i的父节点
f[i] = t; // 把路径上的元素的集改为根节点
i = j; // 新的i是原来i的父节点,继续改下一个点
}
return t;
}
参考《算法竞赛——入门到进阶》--罗勇军 第五章 5.1并查集
后续习题有时间会发布,敬请期待;o((>ω< ))o# 并查集

浙公网安备 33010602011771号