并查集
并查集是一种将一个集合以树形结构进行组合的数据结构,用于处理一些不相交集合的合并及查询问题
并查集的主要操作有:初始化、合并、查找
-
初始化:将每个元素的父辈设为自己,即自己形成一组
void init_set(){ for(int i = 1; i <= n; i++){ arr[i] = i; } }
-
查找:查找元素所在集合的根节点
int find_set(int x){ return x == arr[x] ? x : find_set(arr[x]); }
-
合并:将两个元素所在的集合合成一个集合
void union_set(int x,int y){ x = find_set(x); y = find_set(y); if(x != y) arr[x] = arr[y]; }
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
/* 输入: 输出: 6 5 3 Yes 1 2 Yes 1 5 No 3 4 5 2 1 3 1 4 2 3 5 6 */ #include<bits/stdc++.h> using namespace std; typedef long long ll; const int INF = 0x7fffffff; const ll INFL = 0x7fffffffffffffff; int arr[5010]; int n, m, p; void init_set(){ for(int i = 1; i <= n; i++){ arr[i] = i; } } int find_set(int x){ return x == arr[x] ? x : find_set(arr[x]); } void union_set(int x,int y){ x = find_set(x); y = find_set(y); if(x != y) arr[x] = arr[y]; } int main(){ int x, y; cin >> n >> m >> p; init_set(); for(int i = 1; i <= m; i++){ cin >> x >> y; union_set(x,y); } for(int i = 1; i <= p; i++){ cin >> x >> y; int a = find_set(x); int b = find_set(y); if(a == b) cout << "Yes" << endl; else cout << "No" << endl; } return 0; }
复杂度:在上述程序中,查找find_set()、合并union_set()的搜索深度是树的长度,时间复杂度都是O(n),性能比较差。下面我们来介绍查找和合并的优化方法,优化之后的时间复杂度都小于O(\(log_{2}n\))。
合并的优化
在合并元素x和y时先搜索它们的根节点,然后在合并这两个根节点。但这两个根节点的高度不同,如果把高度小的集合合并到高度大的集合上,能减小树的高度,在初始化时用height[i]来记录元素i的高度,在合并时修改
void init_set(){
for(int i = 1; i <= n; i++){
arr[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]++;
arr[x] = y;
}
else{
if(height[x] < height[y]) arr[x] = y;
else arr[y] = x;
}
}
查询的优化
在上面的查询中,查询元素i所属的集合需要搜索路径找到根结点,这条搜索路径可能很长,如果我们让每个元素都与根节点直接相连,那么下次查询时就能在O(1)的时间复杂度中得到结果
int find_set(int x){
if(x != arr[x]) arr[x] = find_set(arr[x]);//路径压缩
return arr[x];
}
这是个递归过程,如果数据规模较大,担心爆栈,下面是非递归代码
int find_set(int x){
int r = x;
while(arr[r] != r) r = arr[r];
int i = x, j;
while(i != r){
j = arr[i];
arr[i] = r;
i = j;
}
return r;
}