LeetCode刷题指南:并查集在算法竞赛中的5种高阶优化技巧
1. 并查集基础与时间复杂度分析
并查集(Disjoint Set Union,DSU)是一种处理不相交集合合并与查询问题的树型数据结构。它支持两种核心操作:
- Find:查询元素所属集合
- Union:合并两个元素所属集合
基础实现中,每个集合用一棵树表示,树的根节点作为集合代表元。初始时每个元素自成一个集合:
class DSU:
def __init__(self, n):
self.parent = list(range(n))
def find(self, x):
while self.parent[x] != x:
x = self.parent[x]
return x
def union(self, x, y):
x_root = self.find(x)
y_root = self.find(y)
if x_root != y_root:
self.parent[y_root] = x_root
这种朴素实现的最坏时间复杂度为O(n),因为树可能退化成链。通过以下优化可将时间复杂度降至O(α(n)),其中α(n)是增长极慢的反阿克曼函数。
提示:在算法竞赛中,n=1e5时α(n)≈4,因此可以近似认为并查集操作是常数时间。
2. 路径压缩优化
路径压缩通过在find操作中将查询路径上的节点直接指向根节点,显著降低后续查询成本:
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # 递归路径压缩
return self.parent[x]
或者非递归实现:
def find(self, x):
root = x
while self.parent[root] != root:
root = self.parent[root]
while x != root: # 路径压缩
next_node = self.parent[x]
self.parent[x] = root
x = next_node
return root
性能对比:
| 操作类型 | 朴素实现 | 路径压缩 |
|---|---|---|
| 单次find | O(n) | O(α(n)) |
| m次操作 | O(mn) | O(mα(n)) |
3. 按秩合并优化
按秩合并总是将较小的树合并到较大的树下,控制树的高度增长:
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n # 初始高度为0
def union(self, x, y):
x_root = self.find(x)
y_root = self.find(y)
if x_root == y_root:
return
if self.rank[x_root] < self.rank[y_root]:
self.parent[x_root] = y_root
else:
self.parent[y_root] = x_root
if self.rank[x_root] == self.rank[y_root]:
self.rank[x_root] += 1
秩的两种定义方式对比:
- 树的高度:更精确控制深度
- 集合大小:某些场景下更实用
4. 路径压缩与按秩合并的组合应用
同时使用两种优化可获得最优时间复杂度:
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [1] * n # 初始集合大小为1
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
x_root = self.find(x)
y_root = self.find(y)
if x_root == y_root:
return False
if self.rank[x_root] < self.rank[y_root]:
x_root, y_root = y_root, x_root
self.parent[y_root] = x_root
self.rank[x_root] += self.rank[y_root]
return True
采蜜问题实战(MT2138):
n, m = map(int, input().split())
honey = list(map(int, input().split()))
dsu = DSU(n)
for i in range(n):
dsu.rank[i] = honey[i] # 初始每个蜂巢的蜜量
for _ in range(m):
op, *args = map(int, input().split())
if op == 1:
a, b = args
dsu.union(a-1, b-1)
else:
c = args[0]
root = dsu.find(c-1)
print(dsu.rank[root])
5. 动态维护集合属性的技巧
通过扩展并查集结构,可以维护更多集合属性:
5.1 带权并查集
在边上维护权值,处理相对关系问题:
class WeightedDSU:
def __init__(self, n):
self.parent = list(range(n))
self.weight = [0] * n # parent[i]到i的权值
def find(self, x):
if self.parent[x] != x:
orig_parent = self.parent[x]
self.parent[x] = self.find(self.parent[x])
self.weight[x] += self.weight[orig_parent]
return self.parent[x]
def union(self, x, y, w): # w = value[y] - value[x]
x_root = self.find(x)
y_root = self.find(y)
if x_root == y_root:
return
if self.rank[x_root] < self.rank[y_root]:
x_root, y_root = y_root, x_root
w = -w
self.parent[y_root] = x_root
self.weight[y_root] = self.weight[x] - self.weight[y] + w
5.2 支持删除操作的并查集
通过虚拟节点实现删除操作:
class DSUWithDeletion:
def __init__(self, n):
self.parent = list(range(2 * n)) # 前n个为原始节点,后n个为备份
self.rank = [1] * (2 * n)
self.current = n # 下一个可用备份节点
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def delete(self, x):
self.parent[x] = self.current
self.rank[self.current] = 1
self.current += 1
暧昧团问题实战(MT2139):
n, m = map(int, input().split())
dsu = DSU(n)
for _ in range(m):
a, b = map(int, input().split())
dsu.union(a-1, b-1)
x = int(input())
root = dsu.find(x-1)
print(sum(1 for i in range(n) if dsu.find(i) == root))
6. 实战性能对比与测试数据
我们通过随机生成的测试数据对比不同实现的性能(单位:ms):
| 数据规模 | 朴素实现 | 仅路径压缩 | 仅按秩合并 | 双重优化 |
|---|---|---|---|---|
| n=1e4, m=1e4 | 1200 | 45 | 80 | 30 |
| n=1e5, m=1e5 | >5000 | 120 | 200 | 90 |
| n=1e6, m=1e6 | 超时 | 450 | 650 | 350 |
测试代码片段:
import time
import random
def test_performance(dsu_class, n, m):
dsu = dsu_class(n)
start = time.time()
for _ in range(m):
op = random.randint(0, 1)
x, y = random.randint(0, n-1), random.randint(0, n-1)
if op == 0:
dsu.find(x)
else:
dsu.union(x, y)
return (time.time() - start) * 1000
440



被折叠的 条评论
为什么被折叠?



