查並集Disjoint Set(不相交的集合)
阿新 • • 發佈:2021-02-02
技術標籤:學習隨更
目錄
常見兩種操作:
1、合併兩個集合
2、查詢某個元素屬於哪個集合
實現方法一(常用): 每個集合用一棵“有根樹”表示
- 定義陣列Set[1,n];
- Set[ i ] = i ;則 i 表示本集合,並使集合對應樹的根
- Set[ i ] = j ;若 j 不等於 i ,則 j 是 i 的父節點
舉個例子:
Set(i) | 1 | 2 | 3 | 2 | 1 | 3 | 4 | 3 | 3 | 4 |
---|---|---|---|---|---|---|---|---|---|---|
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
具體操作:
1、查:最壞情況O(n)
findx(x){
while(x!= Set[x])x=Set[x];//找父節點“找領導”
return x;
}
2、並:O(1)
mergeset(a,b){//a,b都是根節點(他倆是各自的領導)
Set[a]=b;//表示把a的領導換成了b;
}
避免最壞的情況:(一般不需要)
方法:將深度小的樹合併到深度大的樹上(深度是要另外用陣列計數的)
效果:任意順序合併後,包含k個節點的樹的最大高度不超過 lg(k);
1、查:
findx(x){
while(x!=Set[x])x=Set[x];//找父節點“找領導”
return x;
}
2、並:
mergeset(a,b){//a,b都是根節點(他倆是各自的領導)
if(h[a]==h[b]){
h[a]++;
Set[b]=a;
}
else if(h[a] <h[b])Set[a]=b;
else Set[b]=a;
}
實現方法二(不常用):
用編號最小的元素標記所在集合;
定義一個數組Set[1,n],其中Set[i]表示元素i所在的集合;
舉個例子:
Set(i) | 1 | 2 | 1 | 4 | 2 | 6 | 1 | 6 | 2 | 2 |
---|---|---|---|---|---|---|---|---|---|---|
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
則不相交集合:
{1,3,7},{4},{2,5,9,10},{6,8}
課件每個不相交集合的第一個元素所對應的Set(0)就為該集合中所有元素所對應的值
具體操作:
1、查:O(1)
findx(x){return Set[ x];}
2、合併:O(n)
mergeset(a,b){
i=min(a,b);
j=max(a,b);
for(k=1;k<=n;k++){
if(Set[k]==j)Set[k]=i;
}
}
查並集的簡單應用
最小生成樹
例題
#include <bits/stdc++.h>
#define ll long long
using namespace std;
//最小生成樹問題
int s[110];
struct node {int a, b, d;}c[5000];
bool cmp(node x,node y){return x.d<y.d;}
int findx(int x){//查並集中的查
while(x!=s[x])x=s[x];
return x;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int n;
while(scanf("%d",&n),n){
int m=n*(n-1)/2;
for(int i=1;i<=m;i++)scanf("%d%d%d",&c[i].a,&c[i].b,&c[i].d);
sort(c+1,c+1+m,cmp);//排序;將邊的權值從小到大排序;
for(int i=1;i<=n;i++)s[i]=i;//查並集一定的步驟:“根的初始化”
int ans=0,g=0;
for(int i=1;i<=m;i++){
if(findx(c[i].a) !=findx(c[i].b) ){//只有根節點不同才能進行;否則就不是樹就成環了
ans+=c[i].d;
s[ findx(c[i].a) ]=findx(c[i].b);//合併
g++;if(g==n-1)break;//這一步可有可無;這一步剪枝;因為是樹所以就是n-1條邊;
}
}
printf("%d\n",ans);
}
return 0;
}