并查集

很多人看到这个名字 并查集就犯迷糊了。 因为他们无法理解 并查集 是什么意思。

其实并查集说白了也就两个功能, 合并查询

合并

假设我们有一个图, 图上有很多独立的点, 合并 就是把两个点连在一起。

查询

还是那个图, 查询 的意思就是看看他们是否连在一起。

并查集的不同之处

看到这里,有些人可能会说:这东西,用 vector 存一下与他连接的点然后遍历不就好了吗?

要什么并查集啊!

但是呢, 并查集与上述的方法有一种很重要的区别:

上面的看的是直接连接, 而并查集是间接连接, 也就是只要他们俩有路可达,就算连接。

让我们来打个比方:

小A 是 小B 的孩子, 小B 是 小C 的孩子。

那么 按照道理来说 小A 和 小C 还是亲人。

并查集怎么实现呢?

初始化

要想实现我们上面的理论,我们需要初始化这个东西:

  1. 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])

我们边往上搜索时,就把路过的点都直接指向最终的祖先,就可以大大缩短时间了。

posted @ 2025-07-11 23:39  fyv233  阅读(8)  评论(0)    收藏  举报