3333
3434以上过程涉及了两个基本操作` find ` 和` connnected ` 。 并查集除了这两个基本操作,还有一个是` union ` 。即将两个集合合并为同一个。
3535
36+ > 为了使得合并之后的树尽可能平衡,一般选择将小树挂载到大树上面,之后的代码模板会体现这一点
37+
3638如图有两个司令:
3739
3840![ ] ( https://tva1.sinaimg.cn/large/007S8ZIlly1ghlufys950j30wp0el0th.jpg )
@@ -54,57 +56,57 @@ def find(self, x):
5456 return x
5557```
5658
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+
5769### connected
5870
71+ 直接利用上面实现好的 find 方法即可。如果两个节点的祖先相同,那么其就联通。
72+
5973``` python
6074def connected (self , p , q ):
6175 return self .find(p) == self .find(q)
6276```
6377
6478### union
6579
80+ 将其中一个节点挂到另外一个节点的祖先上,这样两者祖先就一样了。也就是说,两个节点联通了。
81+
6682``` python
6783def union (self , p , q ):
6884 if self .connected(p, q): return
6985 self .parent[self .find(p)] = self .find(q)
7086```
7187
72- ## 完整代码模板
73-
74- ``` python
75- class UF :
76- parent = {}
77- cnt = 0
78- def __init__ (self , M ):
79- # 初始化 parent 和 cnt
88+ ## 不带权并查集
8089
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+ 平时做题过程,遇到的更多的是不带权的并查集。相比于带权并查集, 其实现过程也更加简单。
9291
93- ## 带路径压缩的代码模板
92+ ### 带路径压缩的代码模板
9493
9594``` python
9695class UF :
97- parent = {}
98- size = {}
99- cnt = 0
10096 def __init__ (self , M ):
97+ self .parent = {}
98+ self .size = {}
99+ self .cnt = 0
101100 # 初始化 parent,size 和 cnt
101+ for i in range (M):
102+ self .parent[i] = i
103+ self .cnt += 1
104+ self .size[i] = 1
102105
103106 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]
108110 return x
109111 def union (self , p , q ):
110112 if self .connected(p, q): return
@@ -122,8 +124,92 @@ class UF:
122124 return self .find(p) == self .find(q)
123125```
124126
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+
125199## 总结
126200
127- 如果题目有连通,等价的关系,那么你就可以考虑并查集,使用并查集的时候要注意路径压缩。
201+ 如果题目有连通,等价的关系,那么你就可以考虑并查集,另外使用并查集的时候要注意路径压缩,否则随着树的高度增加复杂度会逐渐增大。
202+
203+ 对于带权并查集实现起来比较复杂,主要是路径压缩和合并这块不一样,不过我们只要注意节点关系,画出如下的图:
204+
205+ ```
206+ a -> b
207+ ^ ^
208+ | |
209+ | |
210+ x y
211+ ```
212+
213+ 就不难看出应该如何更新拉。
128214
129215本文提供的题目模板是西法我用的比较多的,用了它不仅出错概率大大降低,而且速度也快了很多,整个人都更自信了呢 ^\_ ^
0 commit comments