一、基本概念
- 并查集(取自Union合并、Find查找、Set集合)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的Kruskal算法和最近公共祖先等。。。通俗地理解,朋友的朋友都是我的朋友。
- 并查集支持下面两个操作:
1.合并:合并两个集合。
2.查找:判断两个元素是否在同一个集合。
- 查找:由于规定同一个集合中只存在一个根节点,因此查找就是对给定地结点寻找根结点的过程。
///数组f初始化:
for(int i=1;i<=n;i++) f[i]=i;///n为顶点个数,1到n为顶点编号
int find(int x){///递归
if(f[x]==x) return x;
else return find(f[x]);
}
int find(int x){///非递归
while(x!=f[x]) x=f[x];
return x;
}
查找优化:路径压缩。把当前查询路径上的所有结点的父亲都指向根结点,查找的时候就不需要每次都回塑去找父亲了。复杂度就可以降为O(1)了。
int find(int x){///非递归
int p=x;///把x保存下来
while(f[x]!=x) x=f[x];///寻找根节点
while(f[p] != p){///路径压缩
int t = p;
p = f[p];///回溯父亲结点
f[t] = x;///将结点t改为根节点x
}
return x;
}
int find(int x){///递归版
if(x == f[x]) return x;
else{
int t = find(f[x]);///找根节点
f[x] = t;
return t;
}
}
- 合并:指把两个集合合并成一个集合。只有两个不同集合才能进行合并,保证了在同一个集合中一定不会产生环,并查集产生的每一个集合都是一棵树。
void mergex(int x,int y){
int fa,fb;
fa=getf(x);
fb=getf(y);
if(fa!=fb){
f[fa]=fb;///f[fb]=fa同样可以
}
合并的优化:
///a[] 记录每个根节点对应的树的深度(如果不是根节点,其a相当于以它作为根节点的子树的深度)
void mergex(int x,int y){
if((x=find(x))==find(y)) return;
if(a[x]>=a[y]) f[y]=x;///数组a初始化为1
else{
f[x]=y;
if(a[x]==a[y]) a[y]++;///如果深度相同且根节点不同,则新的根节点的深度+1
}
}
二、相关例题
- 基础并查集: 原题地址HDUOJ
题意:n个城镇间有m条道路,为每两个城镇之间都能联通,问至少还需要修多少条道路。
#include<stdio.h>
int f[1010];
int find(int x){///找根节点
if(x!=f[x]) f[x] = find(f[x]);
return f[x];
}
int merge(int x,int y){
int fx = find(x);
int fy = find(y);
if(fx!=fy) f[fx] = fy;
}
int main(){
int n,m;
while(~scanf("%d",&n)&&n){
scanf("%d",&m);
for(int i=1;i<=n;i++) f[i] = i;
int u,v;
while(m--){
scanf("%d%d",&u,&v);
merge(u,v);
}
int ans = 0;
for(int i=1;i<=n;i++)
if(i==find(i)) ans++;
printf("%d\n",ans-1);///除了根结点
}
return 0;
}
- 基础并查集: 原题地址HDUOJ
#include<stdio.h>
#include<string.h>
int f[100010];
bool flag;
int find(int x){///找根节点
if(x==f[x]) return x;
else return find(f[x]);
}
int merge(int x,int y){
int fx = find(x);
int fy = find(y);
if(fx!=fy) f[fy] = fx;
else flag = true;
}
int main(){
while(1){
int u,v;
int sum = 0;
flag = false;
memset(f,0,sizeof(f));
while(scanf("%d%d",&u,&v)&&u&&v){
if(u==-1&&v==-1) return 0;
if(f[u]==0) f[u] = u;
if(f[v]==0) f[v] = v;
merge(u,v);
}
for(int i=1;i<100010;i++)
if(f[i]==i) sum++;
if(sum>1||flag==true) printf("No\n\n");
else printf("Yes\n\n");
}
return 0;
}
- 带权并查集: 原题地址HDUOJ
题意:输出当前的人刚成为朋友时的社交人数,如果两个人之前都没有出现的话,输出第二个的人数。
#include<stdio.h>
#include<string.h>
#include<map>
#include<string>
#include<iostream>
using namespace std;
map <string,int> ma;
int num[100010];
int f[100010];
int find(int x){
if(f[x]!=x) f[x] = find(f[x]);
return f[x];
}
int merge(int x,int y){
int fx = find(x);
int fy = find(y);
if(fx!=fy) {
f[fx] = fy;
num[fy]+=num[fx];
printf("%d\n",num[fy]);
}
else printf("%d\n",num[fy]);
}
int main(){
int t;
while(scanf("%d",&t)!=EOF){
while(t--){
int n;
scanf("%d",&n);
ma.clear();
for(int i=0;i<100010;i++) {
f[i] = i;
num[i] = 1;
}
string name1,name2;
int len=1;
for(int i=0;i<n;i++){
///scanf("%s %s",&name1,&name2);
cin>>name1>>name2;
if(!ma[name1]) ma[name1] = len++;
if(!ma[name2]) ma[name2] = len++;
if(name1==name2) {
printf("%d\n",num[find(ma[name1])]);
continue;
}
merge(ma[name1],ma[name2]);
}
}
}
return 0;
}
- 种类并查集: 原题地址HDUOJ
题意:昆虫分为同性与异性,num[]==0,即f[i]==i为同性,反之为异性,我们用0来代表同性,1代表异性。求是否找到同性。
#include<stdio.h>
int f[2010],num[2010];
bool flag;
int find(int x){
if(x==f[x]) return x;
int temp = find(f[x]);
num[x] = (num[x]+num[f[x]])%2;
f[x] = temp;
return temp;
}
void merge(int u,int v){
int fu,fv;
fu = find(u);
fv = find(v);
if(fu==fv) {
if(num[u]==num[v]) flag = false;///找到同性昆虫
return;
}
f[fv] = fu;
num[fv] = (num[u]+1-num[v]+2)%2;
}
int main(){
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++){
int n,m;
scanf("%d%d",&n,&m);
flag = true;
for(int j=1;j<=n;j++){
f[j] = j;
num[j] = 0;
}
for(int j=1;j<=m;j++){
int u,v;
scanf("%d%d",&u,&v);
if(flag) merge(u,v);
}
printf("Scenario #%d:\n",i);
if(flag) printf("No suspicious bugs found!\n\n");
else printf("Suspicious bugs found!\n\n");
}
return 0;
}