一、基本概念
- 並查集(取自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;
}