1. 程式人生 > >LeetCode 547 Medium 朋友圈 並查集 多種解法 Python

LeetCode 547 Medium 朋友圈 並查集 多種解法 Python

方法一:    

演算法:並查集

    思路

        本題有很強的並查集題目的特徵,使用並查集資料結構可以比較直觀地解題,先看並查集如何解題:

            遍歷鄰接矩陣M,如果M[i][j]==1即二者是朋友,那麼合併i,j集合,遍歷完整個矩陣M後則剩餘

        的集合數量就是有多少個朋友圈

        --------------------------------------------------------------------------------------------------------------------------------

        並查集

            直觀來講並查集就是將N個元素,同一類元素劃分到一個集合中,集合是有標識的,對某個元素p的集合標識,

        就是id=set_id(p)

        並查集中主要的操作有兩個,find和union,查詢與合併

            查詢find很好說,就是return set_id(p)

            合併union就是求集合的並,比如標識為id_P和id_Q的兩個集合合併,就是將標識是id_P的所有元素,或者將

        id_P內的所有元素的標識id改成id_Q,這樣原來id_P內的所有M個元素和id_Q內的所有N個元素合併,新的集合標識

        是id_Q(或者id_P),元素個數是M+N

            以列表元素構建並查集為例

                初始set_id=[i for i in range(n)],每個元素的標識由下標來指定

                find(p) : return set_id[p]

                union(p,q):

                    如果find(p) == find(q),那沒啥事,return,也不用合併

                    否則,遍歷整個set_id,將set_id = p的變成q,(或者q的變成p)

        以此構造是一種比較直觀的線性表下的並查集,其複雜度為ON,因為要遍歷所有set_id嘛

        -------------------------------------------------------------------------------------------------------------------------------------

        基於線性的並查集,可以構建效率更高的並查集森林

         並查集森林將並查集節點構建成樹狀,合併和查詢的效率更高,根節點作為set_id標識,如下圖,元素2,1,0,3的並查集標識id就是2,元素4和5的set_id就是4,

        即find(2) = 2,find(1) = 2,...,find(4) = 4,find(5) = 5,二者Union後就是右邊這樣,整個樹標識為2,這些元素同屬 一個並查集

                    2               4            2
                   / \             /            /| \
                  1   3   Union   5      -->   1 3  4
                 /                            /      \
                0                            0        5


class DisjointSet:
    def __init__(self,n):
        self.set_id = [i for i in range(n)]
        self.set_size = [1 for _ in range(n)]
        self.count = n
    def find(self,p):
        while p !=self.set_id[p]:
            self.set_id[p] = self.set_id[self.set_id[p]]
            p = self.set_id[p]
        return p
    def union(self,p,q):
        i = self.find(p)
        j = self.find(q)
        if i == j:
            return
        if self.set_size[i] < self.set_size[j]:
            self.set_id[i] = j
            self.set_size[j] += self.set_size[i]
        else:
            self.set_id[j] = i
            self.set_size[i] += self.set_size[j]
        self.count -= 1
class Solution:
    def findCircleNum(self, M):
        """
        :type M: List[List[int]]
        :rtype: int
        """
        disjoint_set = DisjointSet(len(M))
        for i in range(len(M)):
            for j in range(len(M)):
                if M[i][j] == 1 :
                    disjoint_set.union(i,j)
        return disjoint_set.count

方法二:

      DFS/BFS遍歷圖

def findCircleNum1(self, M):
    """
    演算法:DFS
    思路:
        可以將題目轉換為是在一個圖中求連通子圖的問題,給出的N*N的矩陣就是鄰接矩陣,建立N個節點的visited陣列,
        從not visited的節點開始深度優先遍歷,遍歷就是在鄰接矩陣中去遍歷,如果在第i個節點的鄰接矩陣那一行中的第j
        個位置處M[i][j]==1 and not visited[j],就應該dfs到這個第j個節點的位置,
    複雜度分析:
        時間:ON2?遍歷所有節點
        空間:ON,visited陣列
    """
    if M == [] or M[0] == []:
        return 0
    n = len(M)
    visited = [False] * n

    def dfs(i):
        visited[i] = True
        for j in range(n):
            if M[i][j] == 1 and not visited[j]:
                dfs(j)

    counter = 0
    for i in range(n):
        if not visited[i]:
            dfs(i)
            counter += 1
    return counter

def findCircleNum2( M):
    """
    和上面相近,這裡用BFS去遍歷
    """
    if M == [] or M[0] == []:
        return 0
    n = len(M)
    visited = [False] * n

    counter = 0
    for i in range(n):
        if not visited[i]:
            queue = [i]
            while queue:
                index = queue.pop(0)
                visited[index] = True
                for j in range(n):
                    if M[index][j] == 1 and not visited[j]:
                        queue.append(j)
            counter += 1
    return counter