33
33
34
34
以上过程涉及了两个基本操作` find ` 和` connnected ` 。 并查集除了这两个基本操作,还有一个是` union ` 。即将两个集合合并为同一个。
35
35
36
+ > 为了使得合并之后的树尽可能平衡,一般选择将小树挂载到大树上面,之后的代码模板会体现这一点
37
+
36
38
如图有两个司令:
37
39
38
40
![ ] ( https://tva1.sinaimg.cn/large/007S8ZIlly1ghlufys950j30wp0el0th.jpg )
@@ -54,57 +56,57 @@ def find(self, x):
54
56
return x
55
57
```
56
58
59
+ 也可使用递归来实现。
60
+
61
+ ``` py
62
+ def find (self , x ):
63
+ if x != self .parent[x]:
64
+ self .parent[x] = self .find(self .parent[x])
65
+ return self .parent[x]
66
+ return x
67
+ ```
68
+
57
69
### connected
58
70
71
+ 直接利用上面实现好的 find 方法即可。如果两个节点的祖先相同,那么其就联通。
72
+
59
73
``` python
60
74
def connected (self , p , q ):
61
75
return self .find(p) == self .find(q)
62
76
```
63
77
64
78
### union
65
79
80
+ 将其中一个节点挂到另外一个节点的祖先上,这样两者祖先就一样了。也就是说,两个节点联通了。
81
+
66
82
``` python
67
83
def union (self , p , q ):
68
84
if self .connected(p, q): return
69
85
self .parent[self .find(p)] = self .find(q)
70
86
```
71
87
72
- ## 完整代码模板
73
-
74
- ``` python
75
- class UF :
76
- parent = {}
77
- cnt = 0
78
- def __init__ (self , M ):
79
- # 初始化 parent 和 cnt
88
+ ## 不带权并查集
80
89
81
- def find (self , x ):
82
- while x != self .parent[x]:
83
- x = self .parent[x]
84
- return x
85
- def union (self , p , q ):
86
- if self .connected(p, q): return
87
- self .parent[self .find(p)] = self .find(q)
88
- self .cnt -= 1
89
- def connected (self , p , q ):
90
- return self .find(p) == self .find(q)
91
- ```
90
+ 平时做题过程,遇到的更多的是不带权的并查集。相比于带权并查集, 其实现过程也更加简单。
92
91
93
- ## 带路径压缩的代码模板
92
+ ### 带路径压缩的代码模板
94
93
95
94
``` python
96
95
class UF :
97
- parent = {}
98
- size = {}
99
- cnt = 0
100
96
def __init__ (self , M ):
97
+ self .parent = {}
98
+ self .size = {}
99
+ self .cnt = 0
101
100
# 初始化 parent,size 和 cnt
101
+ for i in range (M):
102
+ self .parent[i] = i
103
+ self .cnt += 1
104
+ self .size[i] = 1
102
105
103
106
def find (self , x ):
104
- while x != self .parent[x]:
105
- # 路径压缩
106
- self .parent[x] = self .parent[self .parent[x]];
107
- x = self .parent[x]
107
+ if x != self .parent[x]:
108
+ self .parent[x] = self .find(self .parent[x])
109
+ return self .parent[x]
108
110
return x
109
111
def union (self , p , q ):
110
112
if self .connected(p, q): return
@@ -122,8 +124,92 @@ class UF:
122
124
return self .find(p) == self .find(q)
123
125
```
124
126
127
+ ## 带权并查集
128
+
129
+ 实际上并查集就是图结构,我们使用了哈希表来模拟这种图的关系。 而上面讲到的其实都是有向无权图,因此仅仅使用 parent 表示节点关系就可以了。而如果使用的是有向带权图呢?实际上除了维护 parent 这样的节点指向关系,我们还需要维护节点的权重,一个简单的想法是使用另外一个哈希表 weight 存储节点的权重关系。比如 ` weight[a] = 1 表示 a 到其父节点的权重是 1 ` 。
130
+
131
+ 如果是带权的并查集,其查询过程的路径压缩以及合并过程会略有不同,因为我们不仅关心节点指向的变更,也关心权重如何更新。比如:
132
+
133
+ ```
134
+ a b
135
+ ^ ^
136
+ | |
137
+ | |
138
+ x y
139
+ ```
140
+
141
+ 如上表示的是 x 的父节点是 a,y 的父节点是 b,现在我需要将 x 和 y 进行合并。
142
+
143
+ ```
144
+ a b
145
+ ^ ^
146
+ | |
147
+ | |
148
+ x -> y
149
+ ```
150
+
151
+ 假设 x 到 a 的权重是 w(xa),y 到 b 的权重为 w(yb),x 到 y 的权重是 w(xy)。合并之后会变成如图的样子:
152
+
153
+ ```
154
+ a -> b
155
+ ^ ^
156
+ | |
157
+ | |
158
+ x y
159
+ ```
160
+
161
+ 那么 a 到 b 的权重应该被更新为什么呢?我们知道 w(xa) + w(ab) = w(xy) + w(yb),也就是说 a 到 b 的权重 w(ab) = w(xy) + w(yb) - w(xa)。
162
+
163
+ 当然上面关系式是加法,减法,取模还是乘法,除法等完全由题目决定,我这里只是举了一个例子。不管怎么样,这种运算一定需要满足** 可传导性** 。
164
+
165
+ ### 带路径压缩的代码模板
166
+
167
+ 这里以加法型带权并查集为例,讲述一下代码应该如何书写。
168
+
169
+ ``` py
170
+ class UF :
171
+ def __init__ (self , M ):
172
+ # 初始化 parent,weight
173
+ self .parent = {}
174
+ self .weight = {}
175
+ for i in range (M):
176
+ self .parent[i] = i
177
+ self .weight[i] = 0
178
+
179
+ def find (self , x ):
180
+ if self .parent[x] != x:
181
+ ancestor, w = self .find(self .parent[x])
182
+ self .parent[x] = ancestor
183
+ self .weight[x] += w
184
+ return self .parent[x], self .weight[x]
185
+ def union (self , p , q , dist ):
186
+ if self .connected(p, q): return
187
+ leader_p, w_p = self .find(p)
188
+ leader_q, w_q = self .find(q)
189
+ self .parent[leader_p] = leader_q
190
+ self .weight[leader_p] = dist + w_q - w_p
191
+ def connected (self , p , q ):
192
+ return self .find(p)[0 ] == self .find(q)[0 ]
193
+ ```
194
+
195
+ 典型题目:
196
+
197
+ - [ 399. 除法求值] ( https://leetcode-cn.com/problems/evaluate-division/ )
198
+
125
199
## 总结
126
200
127
- 如果题目有连通,等价的关系,那么你就可以考虑并查集,使用并查集的时候要注意路径压缩。
201
+ 如果题目有连通,等价的关系,那么你就可以考虑并查集,另外使用并查集的时候要注意路径压缩,否则随着树的高度增加复杂度会逐渐增大。
202
+
203
+ 对于带权并查集实现起来比较复杂,主要是路径压缩和合并这块不一样,不过我们只要注意节点关系,画出如下的图:
204
+
205
+ ```
206
+ a -> b
207
+ ^ ^
208
+ | |
209
+ | |
210
+ x y
211
+ ```
212
+
213
+ 就不难看出应该如何更新拉。
128
214
129
215
本文提供的题目模板是西法我用的比较多的,用了它不仅出错概率大大降低,而且速度也快了很多,整个人都更自信了呢 ^\_ ^
0 commit comments