Skip to content

Commit 296bf78

Browse files
author
lucifer
committed
feat: 带权并查集
1 parent df50d67 commit 296bf78

File tree

1 file changed

+114
-28
lines changed

1 file changed

+114
-28
lines changed

thinkings/union-find.md

Lines changed: 114 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
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
6074
def connected(self, p, q):
6175
return self.find(p) == self.find(q)
6276
```
6377

6478
### union
6579

80+
将其中一个节点挂到另外一个节点的祖先上,这样两者祖先就一样了。也就是说,两个节点联通了。
81+
6682
```python
6783
def 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
9695
class 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

Comments
 (0)