2021.11.28——并查集


一、关于并查集

1.1 并查集概念引入

并查集,顾名思义,就是合并查询集合,是一种特殊的树状数据结构。一般是用来解决元素分组的问题,或是管理一些不相交的集合,他支持两种操作:1.合并(Union)2.查询(Find),比较典型的问题就是找亲戚,最小生成树的Kruskal算法等等,我们可以考虑使用并查集进行维护。。

并查集的主要思想是用一个集合中的元素来代表这个集合。

这么说可能有点抽象,有人用武侠的例子来生动刻画了这个并查集,有兴趣可以了解一下 链接在这.

简单来说,就是有一堆不认识的人,他们需要拉帮结派,这里需要解决两个问题,第一,怎么让他们结盟(合并),第二,怎么知道他们是不是同一个帮派(查询)

1.2 合并

根据这几张图可以感受一下他们是怎么样建立关系的


并查集1.jpg

初始状态下大家都是分散的,各自成一派,所以自己的教主都是自己


并查集2.jpg

后面2和3想加入1的帮派,于是将他们的教主设为1,这样我就只要知道他们教主是谁,就知道他们是不是这帮派的人
并查集3.jpg

5 6和4同盟


并查集4.jpg

4又和1同盟了,那么大家全都是一家人,英文我们的教主都是1,咱们的头是一样的,那肯定是一帮子人。
并查集5.jpg

最后的树结构是这样的

另外提一下,这里的教主比较专业的叫法是生成元。我们并查集就是通过生成元来判断是不是隶属于这个集合的

1.3 查询

我们已经知道了他们怎么在同一个集合里的,不就是看他们的教主嘛,那我查他们是不是一路子人,就看他们教主是不是一样的就可以了。

那现在的主要问题就是怎么知道他的教主。

刚刚最后的图样很明显,是一个树形的数据结构,我们通过访问其上一级(引入pre数组访问),访问到最上面的一层以后肯定能找到教主的。所以我们很自然想到的方法就是递归

直接上代码。

二、代码实现

初始化(每个人就是一个帮派,自己的教主就是自己本身)

void init(int num)
{
    //一开始是盘散沙,每个人都是各立一派,
    //所以自立为王,他们的上一级(pre)就是他们自己
    for (int i = 1; i <= num; i++)
    {
        pre[i] = i;
    }
}

找生成元(递归思想)

int find(int key)
{
    if (pre[key] == key)
        return key;
    return find(pre[key]);
}

合并

void unite(int x, int y)
//并查集中的‘并’函数
//就是让x,y结成同盟(让x,y的老大都是一样的)
//即隶属于同一个集合内
{
    int root_x = find(x);
    int root_y = find(y);
    if (root_x == root_y)
        return;
    else{
        pre[root_x] = root_y;
    }
}

三、一些优化

3.1 查询路径压缩

我们希望我们只要找到最后的教主就好了,但是每一轮我都这样递归访问,明显会有点麻烦,具体如图。


并查集6.jpg

那有没有解决问题的办法呢?答案是有的,要解决递归引起的问题,我们就在递归上下点功夫,我们是否能将最后递归找到的教主,赋值给每一个之前递归过程中的元素呢?那我们多一个赋值操作就行了,后续我访问的时候,至少这些访问过的节点可以一步到位直接访问到教主,这就是那些大佬们总是说的路径压缩。

并查集7.jpg
int find(int key)
//并查集中的’查‘函数
//找教主,要是不是自己的话,就递归往上找
//这边是优化了一下算法,在递归过程中又赋值一下
//只要查过了一次老大后面就只要调用一次就能找到老大(root)了
{
    if (pre[key] == key)
        return key;
    return pre[key] = find(pre[key]);
}

厉害一点的可以直接一行三元运算符?: 的代码搞定

return x == fa[x] ? x : (fa[x] = find(fa[x]));

3.2 合并路径压缩(按秩合并)

那样优化以后,大家普遍会有一个认识误区,并查集最终生成的树深度是2,但是要注意路径压缩只在查询时进行,也只压缩一条路径,所以并查集最终的结构仍然可能是比较复杂的

在合并问题上,我们也可以留意一下,之前合并的操作的时候,或许你会想着一个问题,为什么要委屈x,让他的帮主上一级变成y的帮主,让y的帮主的上一级变成x的帮主不行吗?其实是可以的,合理的变,在路径上甚至还会有所优化。这是大佬们说的并查集按秩合并。

并查集8.jpg

观察这个图可以发现,树的深度变小了,这意味着访问教主所要递归的次数变少了。具体我们应该把深度小的树合并到深度大的树上去,这样合并以后,到根节点距离变长的节点个数会更少。

代码上需要做的改变是

这里我们引入一个Rank数组,记录每一个节点对应的深度
初始化的时候我们做一点改变,刚开始的深度是1

void init(int n)
{
    for (int i = 1; i <= n; ++i)
    {
        fa[i] = i;
        rank[i] = 1;
    }
}

合并操作代码优化

void unite(int x, int y)
{
    int root_x = find(x);
    int root_y = find(y);
    //已经是同一个集合的话就不需要做了
    if (root_x == root_y)
        return;

    //小树并大树
    if (depth[root_x] > depth[root_y])

        pre[root_y] = root_x;
    else
    {
        if (depth[root_x] == depth[root_y])
            depth[y]++;
        pre[root_x] = root_y;
    }
}

这里只有一种情况是要改变树的深度的,那就是当两棵树的深度相同时,深度要加1。小数并大树的时候,深度还是大树的深度。不会变的

请看,这是两个树深度相同的情况


1

插入到另外一组树后,深度会+1,因为插入的树上面顶着被插树的根节点,所以深度多了一,倒过来插也是一样的。


2

四、题目试炼

2017年第八届 蓝桥杯C组 C/C++决赛 合根植物

题目描述

w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
  这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
输入格式
  第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
  接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
  接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。
  比如:5 * 4 的小格子,编号:
  1 2 3 4
  5 6 7 8
  9 10 11 12
  13 14 15 16
  17 18 19 20

样例输入

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

样例输出

5

其合根情况参考下图:


image.png

我的AC代码附上~

#include <stdio.h>

const int MAX_N = 1000005;
int pre[MAX_N], depth[MAX_N];
int m, n, x, y, T,sum;

void init(int num)
{
    //一开始是盘散沙,
    //每个人都是各立一派,所以自立为王,
    //他们的上一级(pre)就是他们自己
    for (int i = 1; i <= num; i++)
    {
        pre[i] = i;
        depth[i] = 0;
    }
}

int find(int key)
//并查集中的’查‘函数
//找教主,要是不是自己的话,就递归往上找,
//这边是优化了一下算法,在递归过程中又赋值一下,
//只要查过了一次老大后面就只要调用一次就能找到老大(root)了
{
    if (pre[key] == key)
        return key;
    return pre[key] = find(pre[key]);
}

void unite(int x, int y)
//并查集中的‘并’函数
//就是让x,y结成同盟(让x,y的老大都是一样的)
//即隶属于同一个集合内
{
    int root_x = find(x);
    int root_y = find(y);
    if (root_x == root_y)
        return;
    if (depth[root_x] > depth[root_y])
        pre[root_y] = root_x;
    else
    {
        if (depth[root_x] == depth[root_y])
            depth[y]++;
        pre[root_x] = root_y;
    }
}

int main()
{
    // freopen("1.in", "r", stdin);
    // freopen("2.out", "w", stdout);
    scanf("%d %d", &m, &n);
    scanf("%d", &T);
    init(m * n);
    while(T--){
        scanf("%d %d", &x, &y);
        unite(x, y);
    }
    for (int i = 1; i <= m * n; i++)
    {
        if (find(i) == i)
        {
            sum++;
        }
    }
    printf("%d\n", sum);
    return 0;
}

后续关注更新~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • 算法题目 力扣 <连通网络的操作次数>[https://leetcode-cn.com/problems/numb...
    灰气球阅读 2,445评论 0 4
  • 并查集被很多OIer认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并...
    Pecco阅读 353评论 0 0
  • 参考 零基础彻底弄懂"并查集" 1. 举例分析 1.1. 案例说明 快过年了,犯罪分子们也开始为年终奖“奋斗”了,...
    GOGOYAO阅读 1,361评论 0 2
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,709评论 2 59
  • 博主按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步《并查集:集合合并与元素查找》原文地...
    心谭阅读 457评论 0 0