算法详解 【并查集】
并查集概述
针对散列点的集合操作,将哪些点集合到一起,判定哪些点在一个集合是并查集的基本任务。
- 缺点:对于每一个集合,只有一个“群主”,其他的都是 “群员”,群员之间没有层级之分。
- 对于每一个点 i 判定给定一个 pre[ i ];若两个点的 pre[ i ] 相等则属于一个集合,其中 pre[ i ] = i 的点为“群主”。除此之外,若有 pre[ i ] = pre[ pre[ j ] ],形成类似链式关系 , 则递归查找时将 pre[ j ] = pre[ i ],将子点全都归于“群主” 。
- 注意:为了精确的找到某个群员的群主,并且将群员的 pre 全都对齐于群主,我们一般采用 Find( i ) 代替 pre[ i ]
并查集基本操作
初始化
对于每一个点,它一个人属于一个集合,它是它自己的“群主”。
void init()
{
for(int i=1;i<=n;i++){
pre[i] = i;
}
}
查找
对于每一个点,递归查找它的 pre[ ] , 直到最后满足 pre[ i ] = i
int Find(int x)
{
if(x == pre[x]) return x;
return pre[x] = Find(pre[x]);
}
归并
void Union(int x,int y)
{
int a = Find(id[x]),b = Find(id[y]);
if(a == b) return;
pre[a] = b;
}
Almost Union-Find
例题:Almost Union-Find,并查集+删除节点
在并查集中删除一个节点是很麻烦的事情,所以我们引入一个新的东西,用 id[ i ] 代替原有的 i 进行并查集操作,如果有一个点我们想把它从某一个集合中拿出来,就重新给 i 定义一个新的 id[ i ]并将它的状态初始化,一般用(++n)。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int pre[maxn],id[maxn],num[maxn],sum[maxn];
int n,m;
void init()
{
for(int i=1;i<=n;i++){
pre[i]=id[i]=sum[i]= i;
num[i]=1;
}
}
int Find(int x)
{
if(x == pre[x]) return x;
return pre[x] = Find(pre[x]);
}
void Union(int x,int y)
{
int a = Find(id[x]),b = Find(id[y]);
if(a == b) return;
pre[a] = b;
num[b] += num[a];
sum[b] += sum[a];
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF){
init();
int op,x,y;
for(int i=1; i<=m; i++)
{
scanf("%d",&op);
if(op==1){
scanf("%d%d",&x,&y);
Union(x,y);
}
else if(op==2){
scanf("%d%d",&x,&y);
if(Find(id[x]) != Find(id[y])){
num[Find(id[x])]--;
sum[Find(id[x])]-=x;
id[x] = ++n;
pre[id[x]] = id[x];
num[id[x]] = 1;
sum[id[x]] = x;
Union(x,y);
}
}
else if(op==3){
scanf("%d",&x);
printf("%d %d\n",num[Find(id[x])],sum[Find(id[x])]);
}
}
}
return 0;
}