关于并查集

关于并查集

Table of Contents

何为并查集

模版

题目描述

WriteUp

一些补充:

结语&一些题目:

P.S.:当前是挑战3个月冲击省一的第14天,距离CSP-J2开赛还有68天

 这篇文章是由于昨天晚上做了一个有捆绑的01背包,需要使用并查集,我暂时使用dfs代替,遂决定,今天拿下

 (毕竟数据结构这种东西什么 猎奇 算法都有可能用)

何为并查集

简单来说,就是需要对集合进行操作,包括:确认一个元素属于哪一个集合(或判断两个元素是否在同一个集合中)、合并两个集合;

而能做到以非常快的速度完成上面两个操作的数据结构就是————可以快速合并、查询的集合,简称 “并查集”

模版

下面是并查集的模版题:洛谷 P3367 【模板】并查集(难度:提高-)

 我们借助这道题目进行对并查集的讲解

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

## 输入格式

 第一行包含两个整数 \(N,M\) ,表示共有 \(N\) 个元素和 \(M\) 个操作。

 接下来 \(M\) 行,每行包含三个整数 \(Z_i,X_i,Y_i\) 。

 当 \(Z_i=1\) 时,将 \(X_i\) 与 \(Y_i\) 所在的集合合并。

 当 \(Z_i=2\) 时,输出 \(X_i\) 与 \(Y_i\) 是否在同一集合内,是的输出

 `Y` ;否则输出 `N` 。

## 输出格式

 对于每一个 \(Z_i=2\) 的操作,都有一行输出,每行包含一个大写字母,为 `Y` 或者 `N` 。

## 数据范围:

 对于 \(100\%\) 的数据,\(1\le N\le 2\times 10^5\),\(1\le M\le 10^6\),\(1 \le X_i, Y_i \le N\),\(Z_i \in \{ 1, 2 \}\)。

WriteUp

并查集的时间复杂度为 \(\Theta(\alpha(n))\) ,将其换为大O标记,就是平均复杂度;

 这里的 \(\alpha(n)\) 为反阿克曼函数,增长极其缓慢,可以认为一般小于4,也就是常数级;

这里先讲讲并查集是如何做到快速操作的:

 对于每个集合,并查集都选取一个代表元素,简称代表元,对于查询两个元素是否在同一个集合中的操作,只需要看看这两个元素所在集合的代表元是否一样即可;

那么如何找代表元呢:

 当每个元素各自属于一个集合时,他们的代表元为自己,记为 \(w_x\) ;

 若一个集合中有不止1个元素,证明这个集合是由另外的集合合并过来的,因此现在的问题转换成了:合并时如何修改代表元;

每次当我们合并两个集合时(注意:也包括上面单一元素的情况),我们只需要将一个集合中所有元素的代表元设为另一个集合的代表元即可,现在请读者思考一下这样做的时间复杂度是多少;

 显然,是线性的,即 \(O(n)\) 级别,但是我们注意到,查询操作是 \(O(1)\) 的,这提示我们,通过提高查询操作的时间,有可能能够降低合并操作的时间;

这里补充一点树的知识:众所周知,树的一枝上会有分叉,分叉之后可能会有叶,也可能再分叉,我们如果将分叉点和叶都抽象成点,那么紧跟着一个分叉点的叶和这个分叉点就有“父子关系”,称分叉点为叶节点的直接父亲(直接前驱),叶节点称为子节点,分叉点称为父节点;

还记得何为 \(w_x\) 吗,我们定义为元素 \(x\) 所在集合的代表元;

 我们修改一下: \(w_x\) 为元素 \(x\) 的直接父亲,特别的,当这个元素为某个集合的代表元时,它的直接父亲为它自己;

每次合并时,我们先令一个集合为父,另一个为子,找到父集的代表元,将子集的代表元的直接父亲设为父集代表元;

 打个比方:如果小A是B的员工,现在B所在部门要合并到C的部门,那么B的上司为C,C自然也是A的一个领导了;

 这里A、B的所在集合的代表元就是B,而与C合并时直接将B的父亲设为C,自然A也属于C了;

下一个问题是:如何寻找一个集合的代表元:

我们定义: \(find(x)\) 为寻找一个元素所在集合的代表元的函数,显然,当 \(w_x = x\) 时,就找到了代表元,因为根据定义,只有代表元的父节点是其本身;

 现在我们还是回到刚才的比喻:小A现在想找到管自己的最大的一个人,它就先找了B,问了B同样的问题,B又去找了自己的顶头上司C,C发现自己就是最大的,于是告诉B,B告诉A,小A就知道了;

 所以当 \(w_x \not= x\) 时,我们就需要对父节点继续进行 \(find(x)\) 这个操作,直到找到了代表元;

如果读者比较细心,就会发现,目前的复杂度仍然不是常数级,在每次合并时,都需要进行一次 \(find(x)\) ,而 \(find(x)\) 的复杂度是跟深度有关的(比如小A有100个上司,那么查找自己的总经理的询问次数就是100次),是 \(O(n)\) 线性级;

所以,目前的并查集仍然不是完全体,我们需要进一步优化;

 如果我们在小A得到了自己的总经理是C这个结果之后,直接让小A的父节点为C,那么下次小A的下级(如果有的话)询问自己的总经理是谁是,就能跳过B这一步,直接从小A这里得到了答案;

 更一般的,我们如果小A到C之间有100个人,当小A询问完之后,让这里每一个人的父节点都是C,那么下次任何一个人询问的成本都将是1次,常数级,这就是我们想要的;

 也就是大名鼎鼎的————“并查集之 路径压缩”

具体的讲,就是每次执行 \(find(x)\) 这个操作时,我们把所有节点的父节点都设为代表元,这样下一次的合并操作就是 \(O(1)\) 的;

 当然,第一次执行 \(find(x)\) 这个操作时没有这种优化效果;

我们回到最开始的问题:

 我们需要并查集能够在常数时间内完成:1.合并两个元素所在集合 2.判断两个元素是否在同一个集合中;

 现在显然,1我们已经通过代表元和路径压缩做到了,那么2呢;(这里如果读者反应过来了,可以略过)

 对于2,我们只需要判断 \(find(x)\) 是否等于 \(find(y)\) ,就OK了;

 而 \(find(x)\) 在路径压缩下是近似 \(O(1)\) 的,所以,全部的目标达成;

下面给出C++的代码:

#include<iostream>

using namespace std;

const int MAXN = 2e5+7;

int fa[MAXN];

int n,m;

int op,u,v;

int find(int x){

    if(fa[x]==x) return x; //找到了代表元

    fa[x] = find(fa[x]); //询问父节点,并将代表元设为询问节点的父节点

    return fa[x];

}

void union_set(int x,int y){

    int p = find(x),q = find(y);

    fa[q] = p; //将y所在集合的代表元的父节点设为x所在集合的代表元

}

bool if_same(int x,int y){

    return find(x)==find(y);

}

/*

  初始化函数,参数为元素数量;

*/

void init(int x) {

    for (int i=1;i<=x;i++) {

        fa[i] = i;

    }

}

int main() {

    cin>>n>>m;

    init(n);

    for(int i=0;i<m;i++) {

        cin>>op>>u>>v;

        if (op==1) union_set(u,v);

        else {

            bool tmp = if_same(u,v);

            if (tmp) {

                cout<<"Y"<<endl;

            }else {

                cout<<"N"<<endl;

            }

        }

    }

    re

turn 0;

}

一些补充:

1.反阿克曼函数的增长真的非常非常慢,在操作数 \(N \le 10^{18}\) 的情况下都小于4

 2.平均复杂度的说法是不严谨的,应该是均摊复杂度接近常数级

好的,以下是50道关于并查集的判断题: 1. 并查集是一种数据结构,用于维护元素分组情况。 (√) 2. 并查集可以用于解决连通性问题。 (√) 3. 并查集只能用于处理无向图,不能处理有向图。 (×) 4. 并查集的时间复杂度为 O(n log n)。 (×) 5. 并查集中每个元素的代表元素相同,当且仅当这些元素在同一个集合中。 (√) 6. 并查集的初始化过程中,每个元素的父节点都指向自身。 (√) 7. 并查集的合并操作可以通过路径压缩优化。 (√) 8. 并查集的查找操作可以通过路径压缩优化。 (√) 9. 并查集中两个元素所在的集合相同,当且仅当它们的根节点相同。 (√) 10. 并查集中元素个数为 n,最多有 n 个集合。 (×) 11. 并查集中每个元素的父节点都是唯一确定的。 (√) 12. 并查集的路径压缩操作会改变每个元素的父节点。 (√) 13. 并查集的合并操作会改变每个元素的父节点。 (√) 14. 并查集的查找操作会改变每个元素的父节点。 (×) 15. 并查集可以用于解决最小生成树问题。 (√) 16. 并查集可以用于解决最短路径问题。 (×) 17. 并查集中每个元素的父节点最多只有一个。 (√) 18. 并查集中每个元素的子节点最多只有一个。 (×) 19. 并查集中每个元素的子节点可以有多个。 (√) 20. 并查集可以用于解决拓扑排序问题。 (×) 21. 并查集可以用于解决连通块问题。 (√) 22. 并查集的合并操作时间复杂度为 O(log n)。 (×) 23. 并查集的查找操作时间复杂度为 O(log n)。 (×) 24. 并查集的初始化时间复杂度为 O(n)。 (√) 25. 并查集的合并操作可以通过按秩合并优化。 (√) 26. 并查集的查找操作可以通过按秩合并优化。 (√) 27. 并查集中每个元素的祖先节点可以有多个。 (×) 28. 并查集可以用于解决区间合并问题。 (×) 29. 并查集可以用于解决最长公共祖先问题。 (×) 30. 并查集可以用于解决最大连通块问题。 (√) 31. 并查集可以用于解决最小连通块问题。 (×) 32. 并查集可以用于解决图的同构问题。 (×) 33. 并查集可以用于解决图的同构问题。 (×) 34. 并查集可以用于解决图的同构问题。 (×) 35. 并查集可以用于解决图的同构问题。 (×) 36. 并查集可以用于解决图的同构问题。 (×) 37. 并查集可以用于解决图的同构问题。 (×) 38. 并查集可以用于解决图的同构问题。 (×) 39. 并查集可以用于解决图的同构问题。 (×) 40. 并查集可以用于解决图的同构问题。 (×) 41. 并查集可以用于解决图的同构问题。 (×) 42. 并查集可以用于解决图的同构问题。 (×) 43. 并查集可以用于解决图的同构问题。 (×) 44. 并查集可以用于解决图的同构问题。 (×) 45. 并查集可以用于解决图的同构问题。 (×) 46. 并查集可以用于解决图的同构问题。 (×) 47. 并查集可以用于解决图的同构问题。 (×) 48. 并查集可以用于解决图的同构问题。 (×) 49. 并查集可以用于解决图的同构问题。 (×) 50. 并查集可以用于解决图的同构问题。 (×)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛马程序员2025

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值