第十一章 圖論 Part6

haohaoscnblogs發表於2024-09-10

目錄
  • 任務
    • 684. 冗餘連線
      • 思路
    • 685. 冗餘連線 II
      • 思路

任務

684. 冗餘連線

樹可以看成是一個連通且 無環 的 無向 圖。

給定往一棵 n 個節點 (節點值 1~n) 的樹中新增一條邊後的圖。新增的邊的兩個頂點包含在 1 到 n 中間,且這條附加的邊不屬於樹中已存在的邊。圖的資訊記錄於長度為 n 的二維陣列 edges ,edges[i] = [ai, bi] 表示圖中在 ai 和 bi 之間存在一條邊。

請找出一條可以刪去的邊,刪除後可使得剩餘部分是一個有著 n 個節點的樹。如果有多個答案,則返回陣列 edges 中最後出現的那個。

思路

這題的題意感覺很晦澀,目前理解就是遍歷邊將一個一個點加入到並查集中,此時如果發現有已經加入過的兩個同一集合的點,說明形成了環,返回這個最後的邊就是冗餘邊(使得樹變成圖的邊),因為冗餘邊只有一條,按照題目描述全部連線後,這個圖的點數==邊數,按順序找到冗餘邊刪除即可。

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        unionFind = UnionFind(len(edges))
        for i in range(len(edges)):
            if unionFind.is_same(edges[i][0],edges[i][1]):
                return edges[i]
            else:
                unionFind.join(edges[i][0],edges[i][1])
            

class UnionFind:
    def __init__(self,n):
        self.father = [0] * (n+1)
        for i in range(1,n+1):
            self.father[i] = i
        
    # (查) 
    def find(self,u): 
        if u == self.father[u]:
            return u
        else:
            self.father[u] = self.find(self.father[u])  # 路徑壓縮(一直像上層返回最底層的值,並且一直賦值)
            return self.father[u]

    # 判斷 u 和 v 是否找到同一個根
    def is_same(self,u, v): 
        u = self.find(u)
        v = self.find(v)
        return u == v

    # 將 v->u 這條邊加入並查集 (並)
    def join(self,u, v):
        u = self.find(u)  # 尋找 u 的根
        v = self.find(v)  # 尋找 v 的根
        if u == v:
            return  # 如果發現根相同,說明在一個集合,不用兩個節點相連,直接返回
        self.father[v] = u #否則其中一根歸於另一根

685. 冗餘連線 II

在本問題中,有根樹指滿足以下條件的 有向 圖。該樹只有一個根節點,所有其他節點都是該根節點的後繼。該樹除了根節點之外的每一個節點都有且只有一個父節點,而根節點沒有父節點。

輸入一個有向圖,該圖由一個有著 n 個節點(節點值不重複,從 1 到 n)的樹及一條附加的有向邊構成。附加的邊包含在 1 到 n 中的兩個不同頂點間,這條附加的邊不屬於樹中已存在的邊。

結果圖是一個以邊組成的二維陣列 edges 。 每個元素是一對 [ui, vi],用以表示 有向 圖中連線頂點 ui 和頂點 vi 的邊,其中 ui 是 vi 的一個父節點。

返回一條能刪除的邊,使得剩下的圖是有 n 個節點的有根樹。若有多個答案,返回最後出現在給定二維陣列的答案。

思路

從無向圖變成了有向圖,就不是像上題那樣直接任意刪同一集合重複加入的節點構成的邊了,這裡分兩種情況。難點在想到情況2

  • 如果沒有入度為2的點,實際上和無向圖是一樣的,就是將最後讓這個圖形成環拆開即可,和無向圖是一模一樣的
  • 如果存在入度為2的點,則將以這些點為終點的邊收集起來,根據,實際只需要收集最後兩條邊,那麼,結果就在這兩條邊裡,為什麼不是最後一條邊呢,因為有可能刪除最後一條邊後並沒有形成樹(比如同時存在環和入度為2的節點時,必須刪除這兩條邊中構成環的那條邊),所以需要還判斷刪除後是否是一顆樹,這個用並查集也很好判斷。
class Solution:
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]: 
        # 確定所有點的入度,點的值的範圍為[1,n]
        inDegree = [0] * (len(edges) + 1)
        for i in range(len(edges)):  
            inDegree[edges[i][1]] += 1
            
        
        inDegree2List = [] #所有和入度為2的點相連的邊
        #倒序找到所有入度為2涉及到的邊,如果存在入度為2的點,則刪除的邊必在最後兩條中找到
        for i in range(len(edges)-1,-1,-1):
            if inDegree[edges[i][1]] == 2:
                inDegree2List.append(edges[i])
            if len(inDegree2List) == 2:
                break
        if len(inDegree2List) > 0:
            if self.checkIsTree(edges,inDegree2List[0]):
                return inDegree2List[0]
            else:
                return inDegree2List[1]

        unionFind = UnionFind(len(edges))
        for i in range(len(edges)):
            if unionFind.is_same(edges[i][0],edges[i][1]):
                return edges[i]
            else:
                unionFind.join(edges[i][0],edges[i][1])
    
    def checkIsTree(self,edges,delEdge): #確定刪除一條邊後是否是一棵樹
        unionFind = UnionFind(len(edges))
        for i in range(len(edges)):
            if edges[i] == delEdge:
                continue #繼續判斷下一個邊,或並查集新增節點,或發現已經同一集合的節點直接返回False,這裡直接去判斷下一個相當於忽略或者說刪除了當前邊
            if not unionFind.is_same(edges[i][0],edges[i][1]):
                unionFind.join(edges[i][0],edges[i][1])
            else:
                return False
        return True

        
class UnionFind:
    def __init__(self,n):
        self.father = [0] * (n+1)
        for i in range(1,n+1):
            self.father[i] = i
        
    # (查) 
    def find(self,u): 
        if u == self.father[u]:
            return u
        else:
            self.father[u] = self.find(self.father[u])  # 路徑壓縮(一直像上層返回最底層的值,並且一直賦值)
            return self.father[u]

    # 判斷 u 和 v 是否找到同一個根
    def is_same(self,u, v): 
        u = self.find(u)
        v = self.find(v)
        return u == v

    # 將 v->u 這條邊加入並查集 (並)
    def join(self,u, v):
        u = self.find(u)  # 尋找 u 的根
        v = self.find(v)  # 尋找 v 的根
        if u == v:
            return  # 如果發現根相同,說明在一個集合,不用兩個節點相連,直接返回
        self.father[v] = u #否則其中一根歸於另一根               

相關文章