并查集 | 解生成树的实用工具
啥是并查集?
集:
众所周知,集合是一个每个元素只能出现一次的数据结构
那我们可以把集合表示为家谱
例:
集合{1,3,8,45,21}表示为 一棵树?
是的,在算法里,家谱
≈
≈
≈ 树
并:
我们可以把集合并起来
在数学中,称为“A 并 B”
例如:
{1,3,8}并{3,45,21}={1,45,3,8,21}
咦?
这不就是那张家谱吗
用算法语言说:
有点
x
x
x 和点
y
y
y
并起来就是二点中的一个成为另一个父亲,比如这样
温馨提示:不要把
f
a
[
x
]
=
y
fa[x]=y
fa[x]=y 又
f
a
[
y
]
=
x
fa[y]=x
fa[y]=x
不然数学语文算法三方面报错
你猜试错经验从哪来?
我以为是双向的 ,然后错的一塌糊涂
查:
查,顾名思义:查询
比如:
Question:
点 x x x 和点 y y y 在一个集合内吗?
所有的点在一个集合内吗?
Answer:
你怎么这么贪心?
我怎么知道?
呵呵
我们需要写一个
c
h
e
c
k
(
)
check()
check() 函数
啥意思?
只需判断
f
a
[
i
]
=
=
i
fa[i]==i
fa[i]==i 有几个
i
i
i 成立即可
只有一个,TRUE
多了,FALSE
什么?你说一个都没有?
怎么可能?
这是树,不是环!
除非你犯了个错误
温馨提示:不要把 f a [ x ] = y fa[x]=y fa[x]=y 又 f a [ y ] = x fa[y]=x fa[y]=x
不然数学语文算法三方面报错
你猜试错经验从哪来?
我以为是双向的 ,然后错的一塌糊涂
但是我们有一个问题
如果询问45和1在不在一个集合咋办?
我们需要衍生一个定义:
f
a
[
]
=
最老祖先
fa[]=最老祖先
fa[]=最老祖先
换句话说
1的父亲是21,45的父亲也是21
所以1和45在一个集合内
这个叫路径压缩
用递归写即可
判断
i
i
i 是否是最老祖先用
f
a
[
i
]
=
=
i
?
fa[i]==i?
fa[i]==i? 即可
Code Time(题目参考:P1111-修复公路)
#include<bits/stdc++.h>
using namespace std;
int fa[1000005],n,m;
struct node{
int x,y,t;
}a[1000005];
bool operator<(node a,node b){
return a.t<b.t;
}
int find(int x){//路径压缩
if(fa[x]==x){
return x;
}
else{
fa[x]=find(fa[x]);
return fa[x];
}
}
void unio(int x,int y){
fa[find(x)]=find(y);//并
}
bool check(){//check()
int sum=0;
for(int i=1;i<=n;i++){
if(fa[i]==i){
sum++;
}
}
if(sum==1){
return true;
}
else return false;
}
signed main(){
//ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//快读弱化版,在Dijkstra那篇文章讲过
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y>>a[i].t;
}
sort(a+1,a+m+1);
for(int i=1;i<=m;i++){
unio(a[i].x,a[i].y);
if(check()){
cout<<a[i].t;
return 0;
}
}
cout<<-1;
return 0;
}
再讲一道吧(P3367-【模板】并查集)
洛谷中文版
说实话,也挺水的
#include<bits/stdc++.h>
using namespace std;
int fa[1000005],n,m;
struct node{
int x,y;
}a[1000005];
//bool operator<(node a,node b){
// return a.t<b.t;
//}
int find(int x){
if(fa[x]==x){
return x;
}
else{
fa[x]=find(fa[x]);
return fa[x];
}
}
void unio(int x,int y){
fa[find(x)]=find(y);
}
bool question(int x,int y){//切记
if(find(x)==find(y)) return true;//一定是find(x)不然没路径压缩
else return false;
}
signed main(){
//ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
}
while(m--){
int x,y,k;
cin>>k>>x>>y;
if(k==1){
unio(x,y);
}else{
if(question(x,y)) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
return 0;
}
这题很水,就当是双倍经验吧
结语
为啥是解生成树?
K
r
u
s
k
a
l
Kruskal
Kruskal 听过没?
要用并查集来判树
好了,树也不归我管,再见