python实现并查集的操作
并查集的介绍
并查集是一种数据结构,用于处理对 N 个元素的集合划分和判断是否属于同集合的问题。让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
注:定义来自百度百科。
并查集的主要性质
用集合中的某个元素来代表这个集合,该元素称为集合的代表元。
一个集合内的所有元素组织成以代表元为根的树形结构。
对于每一个元素parent[x]指向 x 在树形结构上的父亲节点。如果 x 是根节点,则令 parent[x] = x。
对于查找操作,假设需要确定x 所在的的集合,也就是确定集合的代表元。可以沿着parent[x]不断在树形结构中向上移动,直到到达根节点。
判断两个元素是否属于同一集合,只需要看他们的代表元是否相同即可。
并查集的应用
1、维护无向图的连通性。支持判断两个点是否在同一连通块内,和判断增加一条边是否会产生环。
2、用在求解最小生成树的 Kruskal 算法里。
基本功能实现
创建 union_find 类
创建一个 union_find 的类,并初始化。初始化两个字典,一个保存节点的父节点,另外一个保存父节点的大小。初始化的时候,将节点的父节点设为自身,size 设为 1。
class union_find(object):
def __init__(self, data_list):
self.father_dict = {} # 保存节点的父节点
self.size_dict = {} # 保存父节点的大小
for node in data_list:
self.father_dict[node] = node
self.size_dict[node] = 1
添加 find (查)函数
使用递归的方式来查找父节点
def find(self, node):
father = self.father_dict[node]
if(node != father): # 递归查找父节点
father = self.find(father)
# 在查找父节点的时候,顺便把当前节点移动到父节点上面这个操作算是一个优化
self.father_dict[node] = father
return father
添加 is_same_set 函数
查看两个节点是不是在一个集合里面。通过调用 find 函数,判断两个节点是否是同一个父节点,如果是则判断两个节点属于一个集合。
def is_same_set(self, node_a, node_b):
return self.find(node_a) == self.find(node_b)
添加 union (并)函数
将两个集合合并在一起
def union(self, node_a, node_b):
# 对合并的两个节点做初步判断,判断是否为空
if node_a is None or node_b is None:
return
# 分别查找两个节点的父节点
a_head = self.find(node_a)
b_head = self.find(node_b)
# 当两个节点的父节点不一样时,才能做合并操作
if(a_head != b_head):
a_set_size = self.size_dict[a_head]
b_set_size = self.size_dict[b_head]
# 根据集合的大小做判断,尽量使小集合并到大集合
if(a_set_size >= b_set_size):
self.father_dict[b_head] = a_head
self.size_dict[a_head] = a_set_size + b_set_size
else:
self.father_dict[a_head] = b_head
self.size_dict[b_head] = a_set_size + b_set_size
实例应用
根据参考的 union_find ,完成以下功能的实现
在代码中实现这些步骤:
初始 a = [1,2,3,4,5],并将其添加到并查集里
分别合并[1,2] [3,5] [3,1]
然后判断 2 5 是否为同一个集合
代码实现
class union_find(object):
def __init__(self, data_list):
# 保存节点的父节点
self.father_dict = {}
# 保存父节点的大小
self.size_dict = {}
# 把传进来的数据,分别添加到两个字典中
for node in data_list:
self.father_dict[node] = node
self.size_dict[node] = 1
def find(self, node):
father = self.father_dict[node]
# 递归查找父节点
if (node != father):
father = self.find(father)
# 在查找父节点的时候,顺便把当前节点移动到父节点上面这个操作算是一个优化
self.father_dict[node] = father
return father
def is_same_set(self, node_a, node_b):
# 判断两个节点是否是同一父节点,是,则判断两个节点属于一个集合
return self.find(node_a) == self.find(node_b)
def print_dict(self):
# 输出两个字典中的数据
print(self.father_dict, self.size_dict)
def union(self, node_a, node_b):
# 对合并的两个节点做初步判断,判断是否为空
if node_a is None or node_b is None:
return
# 分别查找两个节点的父节点
a_head = self.find(node_a)
b_head = self.find(node_b)
# 当两个节点的父节点不一样时,才能做合并操作
if (a_head != b_head):
a_set_size = self.size_dict[a_head]
b_set_size = self.size_dict[b_head]
# 根据集合的大小做判断,尽量使小集合合并到大集合
if (a_set_size >= b_set_size):
# a的集合数目大于等于b的,把b的父节点改为a的头结点,即合并到a中去
self.father_dict[b_head] = a_head
# 修改a集合的数目大小
self.size_dict[a_head] = a_set_size + b_set_size
else:
self.father_dict[a_head] = b_head
self.size_dict[b_head] = a_set_size + b_set_size
if __name__ == '__main__':
a = [1, 2, 3, 4, 5]
union_find = union_find(a)
union_find.union(1, 2)
union_find.union(3, 5)
union_find.union(3, 1)
union_find.print_dict()
print(union_find.is_same_set(2, 5))
运行结果如下
解析
第一个字典中,存储的格式为【节点:父节点】
第二个字典中,存储的格式为【父节点:子节点的数目】
我们需要判断的是2与5是否是同一个代表元
根据第一个字典可得2的根节点查找路径为
2–1--3–3
5的根节点查找路径为
5–3
最后比较,两者的代表元相同为3,故返回True