标签:实现 操作 path return log false 最小生成树 rank 就是
本文参考了【算法】并查集(Disjoint Set)和并查集详解
并查集是一种用于处理不相交集合之间合并问题的数据结构,例如求连通子图、判断是否存在环、求最小生成树等。
以判断图中是否有环为例,下图是一个无向图。
首先把每一个顶点都看作是一个集合,逐一地考察每一条边。
A---B
,则把 A 和 B 放入同一个集合,有{A, B}, {C}, {D}, {E}
;A---C
,则把 A 和 C 放入同一个集合,有{A, B, C}, {D}, {E}
;A---D
,则把 A 和 D 放入同一个集合,有{A, B, C, D}, {E}
;C---D
,发现 C 和 D 已经在同一个集合里了,这就说明图中存在环。由这个例子可以看出,并查集的主要操作就是判断两个元素是否在同一个集合中,若不在同一个集合中,则将他们合并。实际上,并查集用一个”代表“来表示一个集合,对于给定的任意一个元素,都可以在常数级别的时间复杂度内找到这个元素的”代表“,即所在的集合。
可见,并查集主要包含两种操作:
并查集用树来表示集合,树中的每一个结点就代表集合中的一个元素,树的根结点就是这个集合的”代表“。要注意的是,树中的父子关系并不能代表元素之间的关系。而树是用数组来表示的,索引 i 处存储的是该元素的父结点的索引,而树根结点的父节点就是它本身。首先初始化一个并查集。
def init(n: int) -> List[int]: # 假设有 n 个元素,即开始时每个元素自成一派
return [i for i in range(n)]
接下来写find
操作。只要每次沿着父节点向上,最终就一定能够找到根结点,即对应集合的”代表“。
def find(x: int, path: List[int]) -> int:
root = x
while path[root] != root:
root = path[root]
return root
最后是合并操作。需要判断两个结点的根结点是否相同,如果不同,则将他们合并。
def unionSet(x: int, y: int, path: List[int]) -> bool:
x_root = find(x)
y_root = find(y)
if x_root == y_root: # 未发生合并,返回False
return False
else: # 需要合并,返回True
path[y_root] = x_root
return True
这时候会发现一个问题,就是之前说的find
操作是在常数时间内的,如果构造树的时候构造出一个一字长蛇阵,那么这个操作显然不是常数级别的。因此需要用到路径压缩。
每次查找时,将结点的父节点改为对应集合的根结点。这样每条经过查找的路径高度都会降为 1。但是由于只在查找时进行路径压缩,因此树的结构仍然有可能是奇形怪状的。
只要修改find
操作的代码就行了。
def find(x: int, path: List[int]) -> int:
if x != path[x]:
path[x] = find(path[x])
return path[x]
另开一个跟path
等长的数组,用来存树的高度。每次合并操作时,总是将较矮树的根结点指向较高树的根结点。
def init(n: int) -> List[int]: # 假设有 n 个元素,即开始时每个元素自成一派
path = [i for i in range(n)]
rank = [0 for i in range(n)]
return path, rank
同时,需要修改合并操作的代码。当两棵树的高度一样时,我们把 y 的根结点指向 x 的根结点。
def unionSet(x: int, y: int, path: List[int]) -> bool:
x_root = find(x)
y_root = find(y)
if x_root == y_root: # 未发生合并,返回False
return False
else: # 需要合并,返回True
if rank[x_root] > rank[y_root]:
path[y_root] = x_root
elif rank[x_root] < rank[y_root]:
path[x_root] = y_root
else:
path[y_root] = x_root
rank[x_root] += 1
return True
标签:实现 操作 path return log false 最小生成树 rank 就是
原文地址:https://www.cnblogs.com/beeblog72/p/13154309.html