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