python实现各种常用算法之数据结构(7)

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 ,完成以下功能的实现

在代码中实现这些步骤:

  1. 初始 a = [1,2,3,4,5],并将其添加到并查集里
  2. 分别合并[1,2] [3,5] [3,1]
  3. 然后判断 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章