LeetCode:1489.找到最小生成树里的关键边和伪关键边

题目:
1489. 找到最小生成树里的关键边和伪关键边

给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。

请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。

请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。

示例 1:
LeetCode:1489.找到最小生成树里的关键边和伪关键边
输入:n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
输出:[[0,1],[2,3,4,5]]
解释:上图描述了给定图。
下图是所有的最小生成树。

LeetCode:1489.找到最小生成树里的关键边和伪关键边
注意到第 0 条边和第 1 条边出现在了所有最小生成树中,所以它们是关键边,我们将这两个下标作为输出的第一个列表。
边 2,3,4 和 5 是所有 MST 的剩余边,所以它们是伪关键边。我们将它们作为输出的第二个列表。

示例2:
LeetCode:1489.找到最小生成树里的关键边和伪关键边
输入:n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
输出:[[],[0,1,2,3]]
解释:可以观察到 4 条边都有相同的权值,任选它们中的 3 条可以形成一棵 MST 。所以 4 条边都是伪关键边。

提示:

  • 2 <= n <= 100
  • 1 <= edges.length <= min(200, n * (n - 1) / 2)
  • edges[i].length == 3
  • 0 <= fromi < toi < n
  • 1 <= weighti <= 1000
  • 所有 (fromi, toi) 数对都是互不相同的。

最小生成树
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。

克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔算法是求连通网的最小生成树的另一种方法。与普里姆算法不同,它的时间复杂度为O(eloge)(e为网中的边数),所以,适合于求边稀疏的网的最小生成树。

克鲁斯卡尔(Kruskal)算法从另一途径求网的最小生成树。其基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点分别在T中不同的连通分量上,则将此边加入到T中;否则,舍去此边而选择下一条代价最小的边。依此类推,直至T中所有顶点构成一个连通分量为止。

解题思路:

Kruskal算法思想、并查集

代码:

class Solution:
    def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]:
        parent = list(range(n))
        def find(index):
            if index != parent[index]:
                parent[index] = find(parent[index])
            return parent[index]
        def union(index1, index2):
            parent[index2] = index1
        
        # 在这里由小到大排序,就是Kruskal的思想
        # 在原有的edge中添加位置索引,方便排序后能得到在原edges中的位置
        sorted_edges = [[index] + i for index, i in enumerate(edges)]
        # 根据 weight 对边进行排序
        sorted_edges = sorted(sorted_edges, key=lambda x:x[-1])


        # 计算最小生成树的权值和total
        total = 0
        for index, (_, x, y, w) in enumerate(sorted_edges):
            rx, ry = find(x), find(y)
            if rx != ry:
                union(rx, ry)
                total += w
        

        # 接下来进行 最小生成树的构造,分为两种情况:
        # 1、先连接当前边,得到所有连通边的权值和tmp_total1
        # 2、去掉当前边,得到所有连通边的权值和tmp_total2

        # 然后total、tmp_total1、tmp_total2进行比较
        # 若total与tmp_total1相等,则代表该边为有效边,否则为无效边直接跳过
        # 然后若tmp_total1不等于tmp_total2,则代表该边为关键边,否则为伪关键边

        key_edge = []  # 关键边列表
        no_key_edge = []  # 非关键边列表
        for i, edge in enumerate(sorted_edges):
            (_, cx, cy, cw) = edge
            # 去掉当前边,形成新的边列表
            tmp_edges = sorted_edges[:i] + sorted_edges[i+1:]


            # 1、先连接当前边,得到连通边的权值和tmp_total1
            total1 = cw
            parent = list(range(n))
            union(cx, cy)
            for index, (_,x,y,w) in enumerate(tmp_edges):
                rx, ry = find(x), find(y)
                if rx != ry:
                    union(rx, ry)
                    total1 += w

            # 2、去掉当前边,得到连通边的权值和tmp_total2
            total2 = 0
            parent = list(range(n))
            for index, (_,x,y,w) in enumerate(tmp_edges):
                rx, ry = find(x), find(y)
                if rx != ry:
                    union(rx, ry)
                    total2 += w

            # 然后total、total1、total2进行比较
            # 若total与total1相等,则代表该边为有效边,否则为无效边直接跳过
            # 然后若total1不等于total2,则代表该边为关键边,否则为伪关键边
            if total1 == total:
                if total1 != total2:
                    key_edge.append(edge[0])
                else:
                    no_key_edge.append(edge[0])
            
        return [key_edge, no_key_edge]

提交记录:
LeetCode:1489.找到最小生成树里的关键边和伪关键边

上一篇:BZOJ 2434: [Noi2011]阿狸的打字机 AC自动机+fail树+线段树


下一篇:CodeForces 1196F K-th Path