并查集
很多人看到这个名字 并查集就犯迷糊了。 因为他们无法理解 并查集 是什么意思。
其实并查集说白了也就两个功能, 合并和查询。
合并
假设我们有一个图, 图上有很多独立的点, 合并 就是把两个点连在一起。
查询
还是那个图, 查询 的意思就是看看他们是否连在一起。
并查集的不同之处
看到这里,有些人可能会说:这东西,用 vector 存一下与他连接的点然后遍历不就好了吗?
要什么并查集啊!
但是呢, 并查集与上述的方法有一种很重要的区别:
上面的看的是直接连接, 而并查集是间接连接, 也就是只要他们俩有路可达,就算连接。
让我们来打个比方:
小A 是 小B 的孩子, 小B 是 小C 的孩子。
那么 按照道理来说 小A 和 小C 还是亲人。
并查集怎么实现呢?
初始化
要想实现我们上面的理论,我们需要初始化这个东西:
- fa 数组, 这个fa数组是用来存储祖先的, 用 \(fa_i\) 表示点 \(i\) 的祖先。在最开始时我们将每个点的祖先都设置成自己。
代码实现
void init(){
for(int i = 0; i < maxn; i++){
fa[i] = i;
}
}
合并
我们先来实现合并, 既然我们要看他们是不是亲戚的话的话, 那么我们只需要找到他们的祖先,
如果他们的祖先相同的话,他们不就是亲戚了嘛!
那我们想要合并的话,就把他们的祖先合并在一起就好了。
但我们要怎么找到他们的祖先呢,我们只要从他们开始,一直往上找祖先,直到找到一个点,他的祖先是自己,那这个点
肯定就是最终的祖先了呗。
以下是代码:
递归式
int find(int x){
if(fa[x] == x) return x; //如果这个人的祖先是他自己,那么说明他就是x的祖先
return find(fa[x]); //一直往上寻找
}
循环式
int find(int x){
while(fa[x]!=x){
x = fa[x];//如果他的祖先不是自己的话就继续往上寻找
}
return x;
}
最终代码
void uni(int x, int y){
fa[find(x)] = find(y); //将x的祖先的祖先设置为y的祖先,使他们的祖先相同(合并)。
}
查询
查询很简单,套用上面的函数,看一下他们的祖先是否相同不就行了。
int connect(int x, int y){
return find(x) == find(y); //看下他们的祖先是否相同
}
来道例题!
代码咱都写出来了,不来道例题怎么行?看看这道 P2256 一中校运会之百米跑
看看描述,
假设一共有 \(N\)(\(2\leq N\leq 2\times 10^4\))个参赛选手。(尼玛全校学生都没这么多吧)
老师会告诉你这 \(N\) 个选手的名字。
接着会告诉你 \(M\)(\(1\leq M\leq 10^6\))句话,即告诉你学生 A 与学生 B 在同一个组里。
如果学生 A 与学生 B 在同一组里,学生 B 与学生 C 也在同一组里,就说明学生 A 与学生 C 在同一组。
然后老师会问你 \(K\)(\(1\leq K\leq 10^6\))句话,即学生 X 和学生 Y 是否在同一组里。
若是则输出 Yes.,否则输出 No.。
乍一看!简简单单,就一模版嘛!可在乍一看!口意!!!!这咋是字符串啊!这咋玩啊?
其实简简单单,把 fa 数组改成一个 map 不就行了?
可是还有一个问题,怎么初始化呢,毕竟我们又不知道他们叫啥?直接在录入名字的时候初始化呗。
话不多说,代码为敬!
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e4+10;
map<string, string>fa;
int n, m,k, cm=1, cw=1;
// 函数也要改成string
string find(string x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void uin(string x, string y){
fa[find(x)] = find(y);
}
bool connect(string x, string y){
return find(x)==find(y);
}
int main(){
cin>>n>>m;
string s, s1;
for(int i=0;i<n;i++){
cin>>s;
fa[s]=s; // 边录入边初始化
}
for(int i=0;i<m;i++){
cin>>s>>s1;
if(find(s)!=find(s1)) fa[find(s)]=find(s1);
}
cin>>k;
for(int i=0;i<k;i++){
cin>>s>>s1;
if(connect(s, s1))cout<<"Yes."<<endl;
else cout<<"No."<<endl;
}
}
一个小小的优化(bushi
在往上搜索寻找祖先时,我们发现如果搜的太久的话,可能会超时。我们需要把路径缩短,会减少时间,
我们可以把find函数修改一下,把 return find(fa[x]) 这一句修改成return fa[x] = find(fa[x])
我们边往上搜索时,就把路过的点都直接指向最终的祖先,就可以大大缩短时间了。

浙公网安备 33010602011771号