並查集總結(python)

並查集 Union Find 算法

定義

並查集(Disjoint-Set)是一種可以動態維護若干個不重疊的集合,並支持合併查詢兩種操作的一種數據結構

基本操作

  1. 合併(Union):合併兩個集合。
  2. 查詢(Find):查詢元素所屬集合。
    實際操作時,我們會使用一個點來代表整個集合,即一個元素的根結點(可以理解爲父親)。

具體實現

我們建立一個數組father_dict字典表示一個並查集,father_dict[i]表示i的父節點。並且設置一個size_dict字典,size_dict[i]表示i的後代節點的個數,包括其本身。

  1. 初始化:每一個點都是一個集合,因此自己的父節點就是自己father_dict[i]=i,size_dict[i]=1.
  2. 查詢:每一個節點不斷尋找自己的父節點,若此時自己的父節點就是自己,那麼該點爲集合的根結點,返回該點。
  3. 合併:合併兩個集合只需要合併兩個集合的根結點,size_dict大喫小,爲了儘可能的降低樹高。

路徑壓縮:實際上,我們在查詢過程中只關心根結點是什麼,並不關心這棵樹的形態(有一些題除外)。因此我們可以在查詢操作的時候將訪問過的每個點都指向樹根,這樣的方法叫做路徑壓縮,單次操作複雜度爲O(logN)

路徑壓縮示例:經過一次FIND節點H,後數據結構的變化。
在這裏插入圖片描述

代碼實現

class UnionFindSet(object):
    """並查集"""
    def __init__(self, data_list):
        """初始化兩個字典,一個保存節點的父節點,另外一個保存父節點的大小
        初始化的時候,將節點的父節點設爲自身,size設爲1"""
        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):
            if father != self.father_dict[father]:    # 在降低樹高優化時,確保父節點大小字典正確
                self.size_dict[father] -= 1
            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 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

應用

朋友圈問題

假如已知有n個人和m對好友關係(存於數組r)如果兩個人是直接或間接的好友(好友的好友的好友…),則認爲他們屬於同一個朋友圈,請寫程序求出這n個人裏一共有多少個朋友圈。假如:n = 5,m = 3,r = {{1 , 2} , {2 , 3} , {4 , 5}}表示有5個人,1和2是好友,2和3是好友4和5是好友,則1、2、3屬於一個朋友圈4、5屬於另一個朋友圈,結果爲2個朋友圈。

輸入:
1
10
0 1
2 3
4 6
2 5
5 4
1 6
10 11
7 9
8 10
7 11
輸出:
2
N = int(input())

for _ in range(N):
    M = int(input())

    nums = []
    maxNum = 0
    for _ in range(M):
        tempTwoNum = list(map(int, input().split()))
        maxNum = max(maxNum, max(tempTwoNum))
        nums.append(tempTwoNum)

    union_find_set = UnionFindSet(list(range(maxNum+1)))
    for i in range(M):
        union_find_set.union(nums[i][0], nums[i][1])


    res_dict = {}
    for i in union_find_set.father_dict:
        rootNode = union_find_set.find(i)
        if  rootNode in res_dict:
            res_dict[rootNode].append(i)
        else:
            res_dict[rootNode] = [i]

    print(res_dict)
    print('朋友圈個數:', len(res_dict.keys()))

親戚

若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。原題鏈接

class UnionFindSet(object):
    """並查集"""
    def __init__(self, data_list):
        """初始化兩個字典,一個保存節點的父節點,另外一個保存父節點的大小
        初始化的時候,將節點的父節點設爲自身,size設爲1"""
        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):
            if father != self.father_dict[father]:    # 在降低樹高優化時,確保父節點大小字典正確
                self.size_dict[father] -= 1
            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 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



N,M,R = map(int, input().split())

uf = UnionFindSet(list(range(1, N+1)))


for _ in range(M):
    num1, num2 = map(int, input().split())
    uf.union(num1, num2)

for _ in range(R):
    num1, num2 = map(int, input().split())
    if uf.is_same_set(num1, num2):
        print('Yes')
    else:
        print('No')

最長連續序列

leetcode 原題鏈接:https://leetcode-cn.com/problems/longest-consecutive-sequence/

給定一個未排序的整數數組,找出最長連續序列的長度。

要求算法的時間複雜度爲 O(n)。

示例:
輸入: [100, 4, 200, 1, 3, 2]
輸出: 4
解釋: 最長連續序列是 [1, 2, 3, 4]。它的長度爲 4。

思路:利用哈希字典,從小往大的FIND,與並查集查找異曲同工,時間複雜度O(1).

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        pre_dict = {}
        for i in nums:
            pre_dict[i] = 1

        res = 0
        for i in pre_dict:
            if i - 1 not in pre_dict:
                y = i + 1
                while y in pre_dict:
                    y += 1

                res = max(res, y - i)

        return res

被圍繞的區域

給定一個二維的矩陣,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 圍繞的區域,並將這些區域裏所有的 ‘O’ 用 ‘X’ 填充。

示例:
X X X X
X O O X
X X O X
X O X X
運行你的函數後,矩陣變爲:
X X X X
X X X X
X X X X
X O X X

解釋:

被圍繞的區間不會存在於邊界上,換句話說,任何邊界上的 ‘O’ 都不會被填充爲 ‘X’。 任何不在邊界上,或不與邊界上的 ‘O’ 相連的 ‘O’ 最終都會被填充爲 ‘X’。如果兩個元素在水平或垂直方向相鄰,則稱它們是“相連”的。

class UnionFindSet(object):
    """並查集"""
    def __init__(self, data_list):
        """初始化兩個字典,一個保存節點的父節點,另外一個保存父節點的大小
        初始化的時候,將節點的父節點設爲自身,size設爲1"""
        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):
            if father != self.father_dict[father]:    # 在降低樹高優化時,確保父節點大小字典正確
                self.size_dict[father] -= 1
            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 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


class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        if not board:
            return
        m, n = len(board), len(board[0])  # row col
        ufs = UnionFindSet(list(range(m*n+1)))


        for i in range(m):
            for j in range(n):
                if board[i][j] == 'O':
                    if i==0 or i==m-1 or j==0 or j==n-1:
                        ufs.union(m*n, i*n+j)
                    else:
                        if board[i+1][j] == 'O':
                            ufs.union((i+1) * n + j, i*n+j)
                        if board[i-1][j] == 'O':
                            ufs.union((i-1) * n + j, i*n+j)
                        if board[i][j-1] == 'O':
                            ufs.union(i * n + (j-1), i*n+j)
                        if board[i][j+1] == 'O':
                            ufs.union(i * n + (j+1), i*n+j)

        for i in range(m):
            for j in range(n):
                rootEdage = ufs.find(m*n)
                if ufs.find(i*n+j) != rootEdage and board[i][j] == 'O':
                    board[i][j] = 'X'
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章