已发布·持续改进
Algorithm
并查集(不相交集合)把元素划分为互不重叠的组,能以近乎常数的时间合并两个组或判断两个元素是否属于同一组。
并查集(Union-Find)又称不相交集合数据结构,用来维护一组被划分为若干互不重叠集合的元素。它只提供两种操作:find 返回元素所在集合的代表元,union 把两个元素所在的集合合并为一个。每个集合以父指针构成的树来存储,树根就是该集合的代表元。
当连接只增不减、又需要反复询问“这两个元素是否连通”时,并查集比每次遍历图快得多。同时使用路径压缩和按秩或按大小合并后,每次操作的均摊代价降到反阿克曼函数 α(n),对任何现实规模的输入它都不超过 4。它是 Kruskal 最小生成树、环检测、连通分量计算以及账户合并等分组问题的标准工具,也是算法面试和刷题中的常见考点。
建议先用一个 parent 数组亲手实现 find 和 union,并手动跟踪在没有优化时树如何退化成一条链。然后逐一加入按大小合并和路径压缩,观察树的深度如何变化,原理就会变得清晰。最后练习连通分量、环检测和 Kruskal 等题目,养成利用 union 返回值的习惯。
find 沿父指针向上找到根(代表元),union 把一个根挂到另一个根之下,从而合并两个集合。
find 把途经的节点直接连到根上,使树变得扁平,之后的调用更快。
把较小的树挂到较大的树下,使每棵树的高度不超过 log n。
两种优化同时使用时,m 次操作只需 O(m α(n)),而反阿克曼函数 α(n) 在实际中不超过 4。
DSU 类用 parent 和 size 两个数组保存集合。find 沿父指针向上,并通过路径减半(把每个节点连到其祖父节点)让树保持扁平;union 把较小的集合挂到较大的集合下,若两个元素已在同一集合则返回 False。示例把 6 个元素合并成三组,然后输出是否属于同一集合以及集合的数量。
union_find.py
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # path halving
x = self.parent[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False
if self.size[ra] < self.size[rb]:
ra, rb = rb, ra
self.parent[rb] = ra
self.size[ra] += self.size[rb]
return True
dsu = DSU(6)
for a, b in [(0, 1), (1, 2), (3, 4)]:
dsu.union(a, b)
print(dsu.find(2) == dsu.find(0)) # True
print(dsu.find(4) == dsu.find(5)) # False
print(len({dsu.find(x) for x in range(6)})) # 3
python union_find.py共六章,带你从安装一步步了解 并查集 的核心概念。
在这里提问、分享经验,交流关于 并查集 的看法。
还没有讨论。来发起第一个吧。
0 条评论
登录 · 登录后即可发表评论。
来发表第一条评论吧。