Skip to content

Commit 2b29631

Browse files
author
lucifer
committed
feat: uf
1 parent fc81de9 commit 2b29631

File tree

1 file changed

+52
-24
lines changed

1 file changed

+52
-24
lines changed

thinkings/union-find.md

+52-24
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,42 @@
11
# 并查集
22

3-
关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴`并查集`标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。
3+
## 背景
44

5-
我这里总结了几道并查集的题目:
5+
相信大家都玩过下面的迷宫游戏。你的目标是从地图的某一个角落移动到地图的出口。规则很简单,仅仅你不能穿过墙。
66

7-
- [547. 朋友圈](../problems/547.friend-circles.md)
8-
- [721. 账户合并](https://leetcode-cn.com/problems/accounts-merge/solution/mo-ban-ti-bing-cha-ji-python3-by-fe-lucifer-3/)
9-
- [990. 等式方程的可满足性](https://github.com/azl397985856/leetcode/issues/304)
10-
- [1202. 交换字符串中的元素](https://leetcode-cn.com/problems/smallest-string-with-swaps/)
11-
- [1697. 检查边长度限制的路径是否存在](https://leetcode-cn.com/problems/checking-existence-of-edge-length-limited-paths/)
7+
![](https://tva1.sinaimg.cn/large/008eGmZEly1goxczig610j30as0ar48s.jpg)
128

13-
上面的题目前面四道都是无权图的连通性问题,第五道题是带权图的连通性问题。两种类型大家都要会,上面的题目关键字都是**连通性**,代码都是套模板。看完这里的内容,建议拿上面的题目练下手,检测一下学习成果。
9+
实际上,这道题并不能够使用并查集来解决。 不过如果我将规则变成,“是否存在一条从入口到出口的路径”,那么这就是一个简单的联通问题,只不过用肉眼去观察实在是太费力了。幸好,我们有计算器可以帮助我们。并查集算法就可以用来解决这个问题,并且比暴力枚举效率高。如果地图不变,而不断改变入口和出口的位置,并查集的效果高的超出你的想象。
10+
11+
另外并查集还可以在人工智能中用作图像人脸识别等,感兴趣的可以查找一下相关资料。
1412

1513
## 概述
1614

17-
并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:
15+
并查集使用的是一种**树型**的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。比如让你求两个人是否间接认识,两个地点之间是否有至少一条路径。上面的例子其实都可以抽象为联通性问题。即如果两个点联通,那么这两个点就有至少一条路径能够将其连接起来。值得注意的是,并查集只能回答“联通与否”,而不能回答诸如“具体的联通路径是什么”。如果要回答“具体的联通路径是什么”这个问题,则需要借助其他算法,比如广度优先遍历。
1816

19-
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
20-
- Union:将两个子集合并成同一个集合。
17+
并查集(Union-find Algorithm)定义了两个用于此数据结构的操作:
2118

22-
初始化每一个点都是一个连通域,类似下图:
23-
24-
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4f8vpp3j30p9024jra.jpg)
19+
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
2520

26-
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数
21+
- Union:将两个子集合并成同一个集合
2722

2823
## 形象解释
2924

3025
比如有两个司令。 司令下有若干军长,军长下有若干师长。。。
3126

32-
我们如何判断某两个师长是否属于同一个司令呢(连通性)?
27+
### 判断两个节点是否联通
28+
29+
我们如何判断某两个师长是否归同一个司令管呢(连通性)?
3330

3431
![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlufxh5lhj30gs0bzwet.jpg)
3532

36-
很简单,我们顺着师长,往上找,找到司令。 如果两个师长找到的是同一个司令,那么就属于同一个司令。我们用 parent[x] = y 表示 x 的 parent 是 y,通过不断沿着搜索 parent 搜索找到 root,然后比较 root 是否相同即可得出结论
33+
很简单,我们顺着师长,往上找,找到司令。 如果两个师长找到的是同一个司令,那么两个人就归同一个司令管
3734

38-
以上过程涉及了两个基本操作`find``connnected`。 并查集除了这两个基本操作,还有一个是`union`。即将两个集合合并为同一个
35+
代码上我们可以用 parent[x] = y 表示 x 的 parent 是 y,通过不断沿着搜索 parent 搜索找到 root,然后比较 root 是否相同即可得出结论。 这里的 root 实际上就是上文提到的**集合代表**
3936

40-
> 为了使得合并之后的树尽可能平衡,一般选择将小树挂载到大树上面,之后的代码模板会体现这一点
37+
这个不断往上找的操作,我们一般称为 find,使用 ta 我们可以很容易地求出两个节点是否连通。
38+
39+
### 合并两个联通区域
4140

4241
如图有两个司令:
4342

@@ -51,6 +50,14 @@
5150

5251
## 核心 API
5352

53+
首先我们初始化每一个点都是一个连通域,类似下图:
54+
55+
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4f8vpp3j30p9024jra.jpg)
56+
57+
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数进行合并。
58+
59+
> 这里的代表就是上面的“司令”。
60+
5461
### find
5562

5663
```python
@@ -70,12 +77,16 @@ def find(self, x):
7077
return x
7178
```
7279

73-
(这里我进行了路径的压缩)
80+
这里我在递归实现的 find 过程进行了路径的压缩,每次往上查找之后都会将树的高度降低到 2。
81+
82+
这有什么用呢?我们知道每次 find 都会从当前节点往上不断搜索,直到到达根节点,因此 find 的时间复杂度大致相等于节点的深度,最坏的情况节点的深度等于树的高度,树的高度如果不加控制则可能为节点数,因此 find 的时间复杂度可能会退化到 $O(n)$。而如果进行路径压缩,那么树的平均高度不会超过 $logn$,具体证明略。不过给大家画了一个图来辅助大家理解。
7483

7584
比如对于如下的一个图:
7685

7786
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4g5jyb9j30og05naaa.jpg)
7887

88+
图中 r:1 表示 秩为 1,r 是 rank 的简写。这里的秩其实对应的就是上文的 size。(下同,不再赘述)
89+
7990
调用 find(0) 会逐步找到 3 ,在找到 3 的过程上会将路径上的节点都指向根节点。
8091

8192
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4i1vrclg30ni05wtj9.gif)
@@ -101,10 +112,12 @@ def connected(self, p, q):
101112

102113
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4avz4iej30lv04rmx9.jpg)
103114

104-
图中 r:1 表示 秩为 1,r 是 rank 的简写。这里的秩其实对应的就是上文的 size。
105-
106115
如果我们将 0 和 7 进行一次合并。即 `union(0, 7)` ,则会发生如下过程。
107116

117+
- 找到 0 的根节点 3
118+
- 找到 7 的根节点 6
119+
- 将 6 指向 3。(为了使得合并之后的树尽可能平衡,一般选择将小树挂载到大树上面,下面的代码模板会体现这一点。3 的秩比 6 的秩大,这样更利于树的平衡,避免出现极端的情况)
120+
108121
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmm4btv06yg30ni05wwze.gif)
109122

110123
代码:
@@ -115,6 +128,8 @@ def union(self, p, q):
115128
self.parent[self.find(p)] = self.find(q)
116129
```
117130

131+
这里我并没有判断秩的大小关系,目的是方便大家理清主脉络。完整代码见下面代码区。
132+
118133
## 不带权并查集
119134

120135
平时做题过程,遇到的更多的是不带权的并查集。相比于带权并查集, 其实现过程也更加简单。
@@ -156,7 +171,7 @@ class UF:
156171

157172
## 带权并查集
158173

159-
实际上并查集就是图结构,我们使用了哈希表来模拟这种图的关系。 而上面讲到的其实都是有向无权图,因此仅仅使用 parent 表示节点关系就可以了。而如果使用的是有向带权图呢?实际上除了维护 parent 这样的节点指向关系,我们还需要维护节点的权重,一个简单的想法是使用另外一个哈希表 weight 存储节点的权重关系。比如 `weight[a] = 1 表示 a 到其父节点的权重是 1`
174+
上面讲到的其实都是有向无权图,因此仅仅使用 parent 表示节点关系就可以了。而如果使用的是有向带权图呢?实际上除了维护 parent 这样的节点指向关系,我们还需要维护节点的权重,一个简单的想法是使用另外一个哈希表 weight 存储节点的权重关系。比如 `weight[a] = 1 表示 a 到其父节点的权重是 1`
160175

161176
如果是带权的并查集,其查询过程的路径压缩以及合并过程会略有不同,因为我们不仅关心节点指向的变更,也关心权重如何更新。比如:
162177

@@ -246,9 +261,22 @@ return True
246261

247262
- [684. 冗余连接](https://leetcode-cn.com/problems/redundant-connection/solution/bing-cha-ji-mo-ban-ben-zhi-jiu-shi-jian-0wz2m/)
248263
- [Forest Detection](https://binarysearch.com/problems/Forest-Detection)
249-
250264
- 最小生成树经典算法 Kruskal
251265

266+
## 练习
267+
268+
关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴`并查集`标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。
269+
270+
我这里总结了几道并查集的题目:
271+
272+
- [547. 朋友圈](../problems/547.friend-circles.md)
273+
- [721. 账户合并](https://leetcode-cn.com/problems/accounts-merge/solution/mo-ban-ti-bing-cha-ji-python3-by-fe-lucifer-3/)
274+
- [990. 等式方程的可满足性](https://github.com/azl397985856/leetcode/issues/304)
275+
- [1202. 交换字符串中的元素](https://leetcode-cn.com/problems/smallest-string-with-swaps/)
276+
- [1697. 检查边长度限制的路径是否存在](https://leetcode-cn.com/problems/checking-existence-of-edge-length-limited-paths/)
277+
278+
上面的题目前面四道都是无权图的连通性问题,第五道题是带权图的连通性问题。两种类型大家都要会,上面的题目关键字都是**连通性**,代码都是套模板。看完这里的内容,建议拿上面的题目练下手,检测一下学习成果。
279+
252280
## 总结
253281

254282
如果题目有连通,等价的关系,那么你就可以考虑并查集,另外使用并查集的时候要注意路径压缩,否则随着树的高度增加复杂度会逐渐增大。

0 commit comments

Comments
 (0)