【知识点】集合与并查集

在前几篇文章当中,我们已经讨论了许多有关数论的知识点了,因此 Macw 决定写几篇数据结构的文章缓一缓。(整天写数论相关的内容容易自闭(bushi))。

今天我们将会围绕一个新的数据结构,并查集(Disjoint Set Union)来展开。

集合与集合的常见操作

在谈论到并查集的时候,首先讨论一个前置知识点,即 集合(Set)的概念。集合是一种无序且不重复的元素集,用数学可以表示为 S = { a , b , c , …   } S = \{a, b, c, \dots\} S={a,b,c,},其中 S S S 是这个集合的名字, a , b , c a, b, c a,b,c 等就是这个集合中的元素。

集合的应用非常的广泛。举一个大家生活中都比较熟悉的例子,运动会报名人数统计。假设目前有两个集合,分别表示参加篮球比赛的选手和参加羽毛球比赛的选手,写作:

B a s k e t b a l l = { Alice , Bob , Cindy , Richard , Tom , Fred } |参加篮球比赛的选手 Basketball = \{\text{Alice}, \text{Bob}, \text{Cindy}, \text{Richard}, \text{Tom}, \text{Fred}\} | \text{参加篮球比赛的选手} Basketball={Alice,Bob,Cindy,Richard,Tom,Fred}参加篮球比赛的选手
B a d m i n t o n = { Vincent , Cindy , Tom , Richard , Selina , Hans } ∣ 参加羽毛球比赛的选手 Badminton = \{\text{Vincent}, \text{Cindy}, \text{Tom}, \text{Richard}, \text{Selina}, \text{Hans}\} | \text{参加羽毛球比赛的选手} Badminton={Vincent,Cindy,Tom,Richard,Selina,Hans}参加羽毛球比赛的选手

对于多个集合而言,设 A A A B B B 是两个集合,常见的有以下操作

  1. 交集(Intersection):求出两个集合中共同包含的元素,写作 A ∩ B A \cap B AB
  2. 并集(Union):求出两个集合中所有的元素并去重,写作 A ∪ B A \cup B AB
  3. 差集(Difference):求出在一个集合 A A A 中但不在另一个集合 B B B 中的元素,写作 A ∖ B A \setminus B AB

举例而言:

  • 如果我们想要求出既参加了篮球比赛,又参加了羽毛球比赛的同学,可以使用交集操作 B a s k e t b a l l ∩ B a d m i n t o n = { Cindy , Tom , Richard } Basketball \cap Badminton = \{\text{Cindy}, \text{Tom}, \text{Richard}\} BasketballBadminton={Cindy,Tom,Richard}。表示 Cindy, Tom 和 Richard 三个人既参加了羽毛球比赛,又同时参加了篮球比赛。
  • 同理,如果我们想要求出所有参赛的同学,可以使用并集的操作 B a s k e t b a l l ∪ B a d m i n t o n = { Alice , Bob , Cindy , Richard , Tom , Fred , Vincent , Selina , Hans } Basketball \cup Badminton = \{\text{Alice}, \text{Bob}, \text{Cindy}, \text{Richard}, \text{Tom}, \text{Fred}, \text{Vincent}, \text{Selina}, \text{Hans}\} BasketballBadminton={Alice,Bob,Cindy,Richard,Tom,Fred,Vincent,Selina,Hans}。表示这些同学是所有参赛的选手。
  • 如果我们想要求出参加了篮球比赛,但没有参加羽毛球比赛的同学,可以使用差集的操作 B a s k e t b a l l ∖ B a d m i n t o n = { Alice , Bob , Fred } Basketball \setminus Badminton = \{\text{Alice}, \text{Bob}, \text{Fred}\} BasketballBadminton={Alice,Bob,Fred}。表示 Alice, Bob 和 Fred 三人只参加了篮球比赛,没有参加羽毛球比赛。

在介绍完集合的基本操作后,我们将目光着重放在计算 并集 的过程中,这也是今天所要讲的并查集算法的重要组成部分之一。

并查集算法

并查集是一种树形的数据结构,用于处理一些不相交的集合(指的是两个集合的交集为空,即两个集合没有共同的元素),并支持 合并 和 查询 的操作。

  • 查询(Find):查找某个元素所属的集合。通常通过路径压缩(Path Compression)优化,以加快查找速度。
  • 合并(Union):合并两个集合。通常通过按秩合并(Union by Rank)优化,以保证树的高度较低,提高效率。

并查集广泛应用于解决动态连通性问题,如图的连通分量、网络连接、最小生成树等。

并查集代码的实现

在并查集中,每一个集合都可以被看作成一棵树。而集合的标识符也可以被表示为这一棵树的根节点。与此同时,如果在一个场景当中有许多的树,那么这些树将会构成一个森林。

当我们想要找到某一个节点在哪个集合当中,本质上就是在寻找这个节点的根节点编号。若我们想要合并两个集合,本质上就是将一颗树的根节点指向另一棵树的根节点。这样子两棵树就被合并成了一棵树,集合的合并操作也因此完成了。

推荐算法可视化网站:Visualgo.net,各位可以自行访问网站观看并查集算法逻辑的可视化演示。

第一步:定义变量和初始化。

首先,我们需要定义并初始化以个向量/数组,一个用于存储每个元素的父节点。一开始将所有元素的父节点都设置为该节点本身。

vector<int> parent;

void initialize(int n) {
    parent.resize(n);
    for (int i = 0; i < n; ++i) {
        parent[i] = i;
    }
}
第二步:实现查询操作。

接下来,我们实现并查集的 find 操作,用于查找某个元素所属的集合。在查找元素所属的集合时,本质上就是在查找这个集合的父元素。该方法可以通过递归的方式来实现。其中,递归的终止条件就是某一个节点的父节点是该节点本身,代表这个节点是这个集合(树)的根节点。

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

在此基础上,我们还可以对算法进行时间上的优化,也就是对搜寻路径进行压缩。路径压缩的基本思想是在执行查找操作时,将路径上的每个节点都直接连接到根节点上,从而降低整个树的高度。

int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]); // 路径压缩
    }
    return parent[x];
}

路径压缩可以显著减少树的高度,使得查找操作的时间复杂度接近于常数级别,提高了并查集的效率。

第三步:实现合并操作。

然后,我们实现并查集的 union 操作,用于合并两个集合。如果两个元素所指向的父元素是同一个,那么可以说明这两个元素存在于同一个集合当中,不需要进行合并操作。否则就将一个元素添加到另一个集合之中。

void unionSets(int x, int y) {
    int rootX = find(x);
    int rootY = find(y);
    
    if (rootX != rootY) {
        parent[rootY] = rootX;
    }
}

并查集的时间复杂度

并查集的时间复杂度取决于其两个主要操作:查找 和 合并。

查找操作(Find)

在普通的并查集中,如果不进行路径压缩优化,find 操作的时间复杂度与树的高度成正比。最坏情况下,树的高度可以达到 O ( n ) O(n) O(n),其中 n n n 是并查集中元素的数量。

但是,在应用了路径压缩优化的情况下,find 操作的均摊时间复杂度接近于常数级别。具体来说,经过路径压缩的并查集的 find 操作的时间复杂度可以表示为 O ( α ( n ) ) O(\alpha(n)) O(α(n)),其中 α ( n ) \alpha(n) α(n) 是阿克曼函数的反函数,增长极其缓慢。对于所有实际应用而言,可以将其约等于为常数时间。

合并操作(Union)

合并操作涉及到查找两个元素所在集合的根节点,并将其中一个根节点连接到另一个根节点上。由于查找过程是常数级别的,而合并两棵树的根节点也是常熟级别的,因此合并操作的时间复杂度也近似于常数时间。

总体复杂度

综上所述,经过路径压缩和按秩合并优化后的并查集,在实际应用中具有非常高效的时间复杂度。对于包含 n n n 个元素的并查集,其 findunion 操作的均摊时间复杂度都接近于常数级别,即 O ( α ( n ) ) O(\alpha(n)) O(α(n))。因此,并查集在解决大规模数据集合动态连通性问题时表现非常出色。

相关引用

  1. GeeksforGeeks. “Introduction to Disjoint Set Data Structure or Union-Find Algorithm.” GeeksforGeeks, 12 May 2023, https://www.geeksforgeeks.org/introduction-to-disjoint-set-data-structure-or-union-find-algorithm/.
  2. CP-Algorithms. “Disjoint Set Union.” CP-Algorithms, https://cp-algorithms.com/data_structures/disjoint_set_union.html.
  3. USA Computing Olympiad. “DSU.” USA Computing Olympiad, https://usaco.guide/gold/dsu?lang=cpp.
  4. Halim, Steven, et al. “Union-Find Disjoint Sets (UFDS) - VisuAlgo.” VisuAlgo.net
  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NOI(全国青少年信息学奥林匹克竞赛)是中国国内最高级别的信息学竞赛,旨在培养青少年信息学创新能力和竞赛实力。NOI的基础知识点之一是并查集,是一种用于解决集合类问题的数据结构。 修路问题可以很好地应用并查集,例如给定一些道路,每条道路连接两个城市,我们要求判断两个城市是否在同一个连通分量中(即是否可以通过已修的道路从一个城市到达另一个城市)。 在解决这个问题时,可以将每个城市看做一个节点,并用并查集来记录节点的父节点,初始时每个节点的父节点为它自身。随着修建道路,将连接的城市节点合并到同一个集合中,即将其中一个城市节点的父节点设为另一个城市节点的父节点。通过不断合并节点,最终我们可以得到若干个连通分量。 当需要判断两个城市是否在同一个连通分量中时,只需查找它们的根节点是否相同。如果根节点相同,则说明两个城市在同一个连通分量中,可以通过已修的道路相互到达;如果根节点不同,则说明两个城市不在同一个连通分量中,无法相互到达。 通过并查集,我们可以高效地解决修路问题,实现基础的连通性判断。在NOI竞赛中,修路问题常常是并查集的一道典型应用题,通过掌握并查集的原理和应用,我们可以更好地解决该类问题,提高信息学竞赛的成绩。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值