小白学并查集(带制作)

Powered by:AB_IN 局外人

并查集例题:戳这!!!

  • 首先什么是并查集?

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
(摘自百度)

  • 是不是觉得很难很抽象?菜鸡一开始也这么觉得,其实看看标程,自己琢磨一下就懂了。
    例题来自 宁波工程学院2020新生校赛(重现赛) L 小梁的道馆
    • 题目描述:
      小梁变强之后决定建设自己的道馆,她特别喜欢去其他的道馆串门。但是有些道馆之间没有道路连通,于是小梁想知道自己能不能去她想去的 道馆。你能帮她写一个程序来查询两个道馆之间是否互相存在道路联通吗?如果存在输出“YES”,反之输出“NO”。

    • 输入描述:
      第一行为三个整数N为道馆个数,M为线路条数,T为查询次数。 ( 1 N < 1000 , 1 M < 1000 , 1 T < 10000 ) (1 \leq N<1000,1 \leq M<1000, 1 \leq T<10000)

      第二行至第M+1行,每行两个整数,代表两个道馆的编号 a , b ( 1 a 1000 , 1 b 1000 ) a,b(1 \leq a \leq 1000,1 \leq b \leq 1000) ,表示这两个道馆之间有道路相连。

      第M+2行至第 M + T + 2 M \text{+} T+2 行,每行两个整数,代表查询这两个道馆。

    • 输出描述:
      T行,每行对应一个查询,假如查询的道馆之间可以连接则输出YES,否则则输出NO。

  1. 好,我们现在来看这道题。道馆可以比作一个个点,一共有N个点。然后给出M条线路,再让你查询T次。
  2. 一开始我怎么想的呢,我想把每次输入的数变成一个列表,排个序,存在另一个大列表里,然后查找时,看看输入在不在大列表里。(python实现)
  3. 但其实这么想是错误的。因为如果输入的是
    1   2 1 \ 2
    2   3 2 \ 3
    那么就不知道1和3其实也连起来了,只知道1和2连起来,2和3连起来了。
    所以这该怎么实现啊?
  4. 为了方便起见,我们举个栗子:
    假设 N=6 然后M=5,线路条数分别是:
3 1
2 1
4 6
5 6
1 4

在这里插入图片描述
ok,是这个样子的。这个其实对应了第一步(预处理),就是将数组的每个数的值都赋上下标的值。(就是圈圈里填上数字)
好,接下来我们执行我们的3 1操作。
在这里插入图片描述
可以看到我故意 让第一个数指向了第二个数,好,我们暂且让 1 1 成为父节点 3 3 成为子节点,可以理解为
> (先输入)子节点—>(后输入)父节点
好,我们继续
在这里插入图片描述
OK,可以看到 2 2 也是 1 1 的小弟(子节点)了。
接下来就是 4 4 5 5 6 6 做大哥(父节点)了。
在这里插入图片描述
到了比较关键步骤了 2 > 4 2—>4
在这里插入图片描述
好了,图到这里画完了 。看这张图,我们知道 2   4 2\ 4 是相连的,但是,其实 1   6 1\ 6 3   5 3\ 5 都是相连的啊,看这张图的箭头我走不过去啊!!
到了关键步骤了!!:核心思想:找老大(父节点)

  • 2   4 2\ 4 在外面可都是小弟啊!怎么办?回去找老大呗!

  • 2 2 的老大是 1 1 4 4 的老大是 6 6 2 > 4 2—>4 说明 2 2 的老大臣服于 4 4 的老大了,所以其实就相当于 1 > 6 1—>6

  • 所以真正的图是这样的:
    在这里插入图片描述
    解读一下核心思想:找老大(父节点),我们就可以做题啦!

    其实判断两个数是否相连,其实就是看他们的父节点是否一致
    举个栗子:判断 3   4 3\ 4 是否连通?
    先看 3 3 3 3 到父节点的路径为
    3 > 1 > 6 3—>1—>6
    再看 4 4 4 4 到父节点的路径为
    4 > 6 4—>6
    不难看出,他们两个的父节点相同,所以他俩连通。


呱唧呱唧
思想我们明白了,那么如何代码实现呢?

  • 首先我们定义个数组
fa[1001]
  • 预处理(前面说过的)
 for(int i = 1;i <= n; i++) fa[i] = i;
  • 接下来输入两个数
scanf("%d %d", &a, &b);
  • 接下来让这两个小弟找老大!
    这里就牵扯到了find函数
int find(int x) {
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}

找老大的过程像是递归,没错,就是递归思想实现!

  • 如果还是自己本身的值,有两种可能:
  1. 个人练习生 (没有老大,之前还没被利用)。
  2. 是最终boss(目前的父节点,如上面例子的最终图中的 6 6
  • 不是自己本身的值:

    说明已经在连线中了,用递归不断调用find函数,来找到父节点,如果递归中碰到了那个 函数值和下标相等的点,那就是父节点,跳出即可。

  • 可能要问了 ,怎么递归啊,怎么赋值啊?
    找到两个的父节点,让fa[左父节点]=右父节点即可,有点数组链表的意思。

	u = find(a); v = find(b);
	fa[u] = v;

如果晕乎的话,菜鸡再举个栗子:
比如 上面我举的栗子中 :

  1. 输入 3   1 3\ 1 3 3 find一找发现 3 3 就是 3 3 的老大, 1 1 find一找发现 1 1 1 1 的老大。
    所以fa[3]=1
  2. 输入 2   4 2\ 4 :我们从前面知道fa[2]=1,开始找 2 2 的父节点,先发现fa[2]!=2,执行else,开始找fa[2](也就是1)的父节点,发现fa[1]=1,那么就return 1回到上一层,fa[2]=1,那么就最终return 1 4 4 同理。
  • 最终一步,输入T,有T次查找,前面说了,看两个数是否连通,就是看两个数父节点是否相同。所以:
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
if(u == v) puts("YES");
else puts("NO");

好啦,介绍完成!
完整代码如下,可以边看代码边debug缕一缕思路哦!

#include <bits/stdc++.h>
using namespace std;
int n, m, t, a, b, u, v;
int fa[1005];

int find(int x) {
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}

int main() {
    scanf("%d %d %d", &n, &m, &t);
    for(int i = 1;i <= n; i++) fa[i] = i;
    for(int i = 1;i <= m; i++) {
        scanf("%d %d", &a, &b);
        u = find(a); v = find(b);
        fa[u] = v;
    }
    while(t--) {
        scanf("%d %d", &a, &b);
        u = find(a); v = find(b);
        if(u == v) puts("YES");
        else puts("NO");
    }
}

完结。
困死了~~


猜你喜欢

转载自blog.csdn.net/qq_45859188/article/details/106879351