第十一章 图论 Part6

任务

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 #否则其中一根归于另一根               
posted @ 2024-09-10 00:00  haohaoscnblogs  阅读(24)  评论(0)    收藏  举报