Skip to content

Commit 0cea483

Browse files
committed
Update 01.Union-Find.md
1 parent 2625547 commit 0cea483

File tree

1 file changed

+43
-15
lines changed

1 file changed

+43
-15
lines changed

Contents/07.Tree/05.Union-Find/01.Union-Find.md

+43-15
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@
3838

3939
在使用「快速查询」思路实现并查集时,我们可以使用一个「数组结构」来表示集合中的元素。数组元素和集合元素是一一对应的,我们可以将数组的索引值作为每个元素的集合编号,称为 $id$。然后可以对数组进行以下操作来实现并查集:
4040

41-
- **当初始化时**将每个元素的集合编号初始化为数组下标索引。则所有元素的 $id$ 都是唯一的,代表着每个元素单独属于一个集合。
41+
- **当初始化时**将数组下标索引值作为每个元素的集合编号。所有元素的 $id$ 都是唯一的,代表着每个元素单独属于一个集合。
4242
- **合并操作时**:需要将其中一个集合中的所有元素 $id$ 更改为另一个集合中的 $id$,这样能够保证在合并后一个集合中所有元素的 $id$ 均相同。
4343
- **查找操作时**:如果两个元素的 $id$ 一样,则说明它们属于同一个集合;如果两个元素的 $id$ 不一样,则说明它们不属于同一个集合。
4444

45-
举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。从下图中可以看出:元素的集合编号就是数组的索引值,代表着每个元素属于一个集合。
45+
举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。
4646

47-
![](https://qcdn.itcharge.cn/images/20220505145234.png)
47+
![基于数组实现:初始化操作](https://qcdn.itcharge.cn/images/20240513150949.png)
4848

49-
当我们进行一系列的合并操作后,比如合并后变为 $\left\{ 0 \right\}, \left\{ 1, 2, 3 \right\}, \left\{ 4 \right\}, \left\{5, 6\right\}, \left\{ 7 \right\}$,合并操作的结果如下图所示。从图中可以看出,在进行一系列合并操作后,下标为 $1$、$2$、$3$ 的元素集合编号是一致的,说明这 $3$ 个 元素同属于一个集合。同理下标为 $5$ 和 $6$ 的元素则同属于另一个集合
49+
从上图中可以看出:数组的每个下标索引值对应一个元素的集合编号,代表着每个元素单独属于一个集合
5050

51-
![](https://qcdn.itcharge.cn/images/20220505145302.png)
51+
当我们进行一系列的合并操作后,比如合并后变为 $\left\{ 0 \right\}, \left\{ 1, 2, 3 \right\}, \left\{ 4 \right\}, \left\{5, 6\right\}, \left\{ 7 \right\}$,合并操作的结果如下图所示。
52+
53+
![基于数组实现:合并操作](https://qcdn.itcharge.cn/images/20240513151310.png)
54+
55+
从上图中可以看出,在进行一系列合并操作后,下标为 $1$、$2$、$3$ 的元素集合编号是一致的,说明这 $3$ 个元素同属于一个集合。同理下标为 $5$ 和 $6$ 的元素则同属于另一个集合。
5256

5357
在快速查询的实现思路中,单次查询操作的时间复杂度是 $O(1)$,而单次合并操作的时间复杂度为 $O(n)$(每次合并操作需要遍历数组)。两者的时间复杂度相差得比较大,完全牺牲了合并操作的性能。因此,这种并查集的实现思路并不常用。
5458

@@ -92,17 +96,41 @@ class UnionFind:
9296

9397
总结一下,我们可以对数组 $fa$ 进行以下操作来实现并查集:
9498

95-
- **当初始化时**将每个元素的集合编号初始化为数组 $fa$ 的下标索引。所有元素的根节点的集合编号不一样,代表着每个元素单独属于一个集合。
99+
- **当初始化时**将数组 $fa$​ 的下标索引作为每个元素的集合编号。所有元素的根节点的集合编号都不一样,代表着每个元素单独属于一个集合。
96100
- **合并操作时**:需要将两个集合的树根节点相连接。即令其中一个集合的树根节点指向另一个集合的树根节点(`fa[root1] = root2`),这样合并后当前集合中的所有元素的树根节点均为同一个。
97101
- **查找操作时**:分别从两个元素开始,通过数组 $fa$ 存储的值,不断递归访问元素的父节点,直到到达树根节点。如果两个元素的树根节点一样,则说明它们属于同一个集合;如果两个元素的树根节点不一样,则说明它们不属于同一个集合。
98102

99-
举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{0\right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。从下图中可以看出:元素的集合编号就是数组 $fa$ 的索引值,代表着每个元素属于一个集合。
103+
举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{0\right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。
104+
105+
![基于森林实现:初始化操作](https://qcdn.itcharge.cn/images/20240513151548.png)
106+
107+
从上图中可以看出:$fa$ 数组的每个下标索引值对应一个元素的集合编号,代表着每个元素属于一个集合。
108+
109+
当我们进行一系列的合并操作后,比如 `union(4, 5)``union(6, 7)``union(4, 7)` 操作后变为 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4, 5, 6, 7 \right\}$​,合并操作的步骤及结果如下图所示。
110+
111+
::: tabs#union
112+
113+
@tab <1>
114+
115+
- 合并 $(4, 5)$:令 $4$ 的根节点指向 $5$,即将 $fa[4]$ 更改为 $5$。
116+
117+
![基于森林实现:合并操作 1](https://qcdn.itcharge.cn/images/20240513154015.png)
118+
119+
@tab <2>
120+
121+
- 合并 $(6, 7)$:令 $6$ 的根节点指向 $7$,即将 $fa[6]$ 更改为 $7$。
122+
123+
![基于森林实现:合并操作 2](https://qcdn.itcharge.cn/images/20240513154022.png)
124+
125+
@tab <3>
126+
127+
- 合并 $(4, 7)$:令 $4$ 的的根节点指向 $7$,即将 $fa[fa[4]]$(也就是 $fa[5]$)更改为 $7$。
100128

101-
![](https://qcdn.itcharge.cn/images/20220507112934.png)
129+
![基于森林实现:合并操作 3](https://qcdn.itcharge.cn/images/20240513154030.png)
102130

103-
当我们进行一系列的合并操作后,比如 `union(4, 5)``union(6, 7)``union(4, 7)` 操作后变为 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4, 5, 6, 7 \right\}$,合并操作的步骤及结果如下图所示。从图中可以看出,在进行一系列合并操作后,`fa[4] == fa[5] == fa[6] == fa[fa[7]]`,即 $4$、$5$、$6$、$7$ 的元素根节点编号都是 $4$,说明这 $4$ 个 元素同属于一个集合。
131+
:::
104132

105-
![](https://qcdn.itcharge.cn/images/20220507142647.png)
133+
从上图中可以看出,在进行一系列合并操作后,`fa[fa[4]] == fa[5] == fa[6] == f[7]`,即 $4$、$5$、$6$、$7$ 的元素根节点编号都是 $4$,说明这 $4$ 个元素同属于一个集合。
106134

107135
- 使用「快速合并」思路实现并查集代码如下所示:
108136

@@ -132,7 +160,7 @@ class UnionFind:
132160

133161
在集合很大或者树很不平衡时,使用上述「快速合并」思路实现并查集的代码效率很差,最坏情况下,树会退化成一条链,单次查询的时间复杂度高达 $O(n)$。并查集的最坏情况如下图所示。
134162

135-
![](https://qcdn.itcharge.cn/images/20220507172300.png)
163+
![并查集最坏情况](https://qcdn.itcharge.cn/images/20240513154732.png)
136164

137165
为了避免出现最坏情况,一个常见的优化方式是「路径压缩」。
138166

@@ -146,7 +174,7 @@ class UnionFind:
146174
147175
下面是一个「隔代压缩」的例子。
148176

149-
![](https://qcdn.itcharge.cn/images/20220509113954.png)
177+
![路径压缩:隔代压缩](https://qcdn.itcharge.cn/images/20240513154745.png)
150178

151179
- 隔代压缩的查找代码如下:
152180

@@ -164,7 +192,7 @@ def find(self, x): # 查找元素根节点的集合
164192
165193
相比较于「隔代压缩」,「完全压缩」压缩的更加彻底。下面是一个「完全压缩」的例子。
166194

167-
![](https://qcdn.itcharge.cn/images/20220507174723.png)
195+
![路径压缩:完全压缩](https://qcdn.itcharge.cn/images/20240513154759.png)
168196

169197
- 完全压缩的查找代码如下:
170198

@@ -195,7 +223,7 @@ def find(self, x): # 查找元素根节点的集合
195223

196224
下面是一个「按深度合并」的例子。
197225

198-
![](https://qcdn.itcharge.cn/images/20220509094655.png)
226+
![按秩合并:按深度合并](https://qcdn.itcharge.cn/images/20240513154814.png)
199227

200228
- 按深度合并的实现代码如下:
201229

@@ -240,7 +268,7 @@ class UnionFind:
240268

241269
下面是一个「按大小合并」的例子。
242270

243-
![](https://qcdn.itcharge.cn/images/20220509094634.png)
271+
![按秩合并:按大小合并](https://qcdn.itcharge.cn/images/20240513154835.png)
244272

245273
- 按大小合并的实现代码如下:
246274

0 commit comments

Comments
 (0)