路径压缩继续优化并查集
在实现的并查集中,在合并操作merge(item1, item2)时,会不管两个元素所在的分组大小,总是将item
1的分组合并到item2的分组,这样可能会导致树的深度无必要地增加:
如果是大树合并到小树上,会导致树的深度增加,进而造成增删改查等操作的代价增大
因此我们要对并查集的合并操作进行优化。
优化合并方式
- 每次合并时,都将小分组的元素往大分组合并,由于本例初始化都是一个元素,对应一个分组,它合并的情况可能会变成如下这样(箭头代表分组之间的edge),类似一颗星状的树:
属性和方法说明
- self.num_groups 分组数量
- self.groups 一个数组,索引代表传入的item的值,元素代表分组编号
- nums_in_each_group 一个数组,索引代表分组编号,元素代表每个分组的元素个数
- count_groups() 获取当前总共分数的数量
- in_the_same_group(item1, item2) 判断两个传入的item是否在同一分组
- which_group(item) 获取传入的item的所在分组
- unite(item1, item2) 合并两个元素到同一分组,小的分组总是会往大的分组合并
Python代码实现及测试
class UF_Tree_Weighted:
def __init__(self, n):
self.num_groups = n
self.groups = [i for i in range(n)]
self.nums_in_each_group = [1 for _ in range(n)]
def count_groups(self):
return self.num_groups
def in_the_same_group(self, item1, item2):
return self.which_group(item1) == self.which_group(item2)
def which_group(self, item):
"""Find item's root------>groups[groups[groups[...groups[item]...]]]"""
while self.groups[item] != item:
item = self.groups[item]
return item
def unite(self, item1, item2):
p = self.which_group(item1)
q = self.which_group(item2)
if p == q:
return
if self.nums_in_each_group[p] <= self.nums_in_each_group[q]:
# Merge the smaller group into the bigger's
self.nums_in_each_group[q] += self.nums_in_each_group[p]
self.nums_in_each_group[p] = 0
# Change the smaller group-number to the bigger's group-number
self.groups[p] = self.groups[q]
else:
# Merge the smaller group into the bigger's
self.nums_in_each_group[p] += self.nums_in_each_group[q]
self.nums_in_each_group[q] = 0
self.groups[q] = self.groups[p]
# Numbers of group subtract 1
self.num_groups -= 1
if __name__ == '__main__':
UF = UF_Tree_Weighted(5)
print(f"The initial number of groups is {UF.num_groups}")
while True:
p = int(input(f'Input the to-be-merge element: '))
q = int(input(f"Merge to the target element's group: "))
if UF.in_the_same_group(p, q):
print(f"They are already in the same group")
continue
UF.unite(p, q)
print(f"The number of groups now is {UF.count_groups()}")
print(UF.groups)
print(f"Elements in each group: {UF.nums_in_each_group}")
运行结果
The initial number of groups is 5
Input the to-be-merge element: 0
Merge to the target element's group: 1
The number of groups now is 4
[1, 1, 2, 3, 4]
Elements in each group: [0, 2, 1, 1, 1]
Input the to-be-merge element: 1
Merge to the target element's group: 2
The number of groups now is 3
[1, 1, 1, 3, 4]
Elements in each group: [0, 3, 0, 1, 1]
Input the to-be-merge element: 3
Merge to the target element's group: 2
The number of groups now is 2
[1, 1, 1, 1, 4]
Elements in each group: [0, 4, 0, 0, 1]
Input the to-be-merge element: 2
Merge to the target element's group: 4
The number of groups now is 1
[1, 1, 1, 1, 1]
Elements in each group: [0, 5, 0, 0, 0]
Input the to-be-merge element:
当调用unite()合并时,合并时不考虑参数的先后位置,而是考虑分组的大小,总是会有小的分组合并到大的分组