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

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