Topic:
给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。
请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。
请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。
Example_1:
输入: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]]
解释:上图描述了给定图。
下图是所有的最小生成树。
注意到第 0 条边和第 1 条边出现在了所有最小生成树中,所以它们是关键边,我们将这两个下标作为输出的第一个列表。
边 2,3,4 和 5 是所有 MST 的剩余边,所以它们是伪关键边。我们将它们作为输出的第二个列表。
Example_2:
输入:n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
输出:[[],[0,1,2,3]]
解释:可以观察到 4 条边都有相同的权值,任选它们中的 3 条可以形成一棵 MST 。所以 4 条边都是伪关键边。
Solution:
本题就是求生成最小生成树时可能被删除的边与一定会被保留的边(伪关键边与关键边)
解出本题需提前了解一些概念
最小生成树
Kruskal算法
以及之前总结好的并查集模板
首先需要对每一条边进行一个排序添加索引命名(添加下标)
之后将edges按照权重 w 从小到大排序 记为 edges_sorted
然后利用并查集计算最小生成树的权值的和为 total
生成关键边列表l_1和伪关键边节点l_2
复习一下关键边的定义
关键边:
若删去某条边,导致最小生成树的权值和增加
(无论在哪个最小生成树中一定会保留的边)
伪关键边:
可能会出现在某些最小生成树中,
但不会出现在所有最小生成树中的边
遍历edges_sorted:
先计算连通此边之后的最小生成树的权值和为total_0
之后计算去掉此边的权值和为total_1
然后和total比较,如果total和total_0相等
则说明是有效边(既不是关键边也不是有效边)
若不是有效边则跳过
非有效边:
连通此边后无法形成最小有效树
(不在所有种类的最小有效树中存在)
若total_0和total_1不相等
(删除此边后其他的边不能形成一个有效的最小生成树)
则满足删去此边,一定会导致最小生成树的权值和增加
此边是关键边
加入关键边列表l_1中
若相等
(删除此边仍能形成一个最小生成树)
则满足删去此边,不一定能导致最小生成树的权值和增加
加入伪关键边列表l_2中
最后将l_1和l_2的列表打包成输出
Code:
class UnionFind:
def __init__(self):
self.father = {
}
def find(self, x):
root = x
while self.father.get(root) is not None:
root = self.father[root]
# 路径压缩
while root != x:
father_code = self.father[x]
self.father[x] = root
x = father_code
return root
def merge(self, x, y):
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
def is_connected(self, x, y):
return self.find(x) == self.find(y)
class Solution:
def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]:
# 添加边的下标
edges = [edge + [i] for i, edge in enumerate(edges)]
# 按权重排序
edges_sorted = sorted(edges, key=lambda x: x[-2])
uf = UnionFind()
num = 1
total = 0
# 最小生成树
for edge in edges_sorted:
a = edge[2] # 边长
b = edge[0]
c = edge[1]
is_connected = uf.is_connected(b, c)
if not is_connected:
uf.merge(b, c)
total += a
n += 1
if num == n:
break
l_1 = list() # 关键边
l_2 = list() # 伪关键边
# 遍历
for i, this_edge in enumerate(edges_sorted):
edges_tmp = edges_sorted[:i] + edges_sorted[i + 1:]
this_x = this_edge[0]
this_y = this_edge[1]
this_w = this_edge[2]
this_i = this_edge[3]
# 先连通此边的总权值和
uf = UnionFind()
total_0 = this_w
uf.merge(this_x, this_y)
for edge in edges_tmp:
x = edge[0]
y = edge[1]
w = edge[2]
if not uf.is_connected(x, y):
uf.merge(x, y)
total_0 += w
# 去掉此边的权值和
uf = UnionFind()
total_1 = 0
for edge in edges_tmp:
x = edge[0]
y = edge[1]
w = edge[2]
if not uf.is_connected(x, y):
uf.merge(x, y)
total_1 += w
# 和total比较,如果total和total0相等,说明是有效边
if total == total_0:
# 如果total_0和total_1不相等,则是关键边
if total_0 != total_1:
l_1.append(this_i)
# 否则为伪关键边
else:
l_2.append(this_i)
return [l_1, l_2]