1.题目基本信息
1.1.题目描述
给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。
请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。
请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。
1.2.题目地址
https://leetcode.cn/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/description/
2.解题方法
2.1.解题思路
无向图的连通性质:取权值为w的边,让小于w的边构成一个一个的连通分量comps,将每个连通分量comp作为一个节点,将w大小的节点与comps这些节点重新构建一个图,记为compG。
compG中的桥即为关键边;w大小的边的两端如果在同一个连通分量内,则既不是关键边也不是伪关键边;余下的都是伪关键边。
2.2.解题步骤
解题步骤请看代码注释
3.解题代码
Python代码
# # ==> 并查集模板(附优化)
class UnionFind():
def __init__(self):
self.roots={}
self.setCnt=0 # 连通分量的个数
# Union优化:存储根节点主导的集合的总节点数
self.rootSizes={}
def add(self,x):
if x not in self.roots:
self.roots[x]=x
self.rootSizes[x]=1
self.setCnt+=1
def find(self,x):
root=x
while root != self.roots[root]:
root=self.roots[root]
# 优化:压缩路径
while x!=root:
temp=self.roots[x]
self.roots[x]=root
x=temp
return root
def union(self,x,y):
rootx,rooty=self.find(x),self.find(y)
if rootx!=rooty:
# 优化:小树合并到大树上
if self.rootSizes[rootx]<self.rootSizes[rooty]:
self.roots[rootx]=rooty
self.rootSizes[rooty]+=self.rootSizes[rootx]
else:
self.roots[rooty]=rootx
self.rootSizes[rootx]+=self.rootSizes[rooty]
self.setCnt-=1
from typing import List
class UndirectedGraphTarjan2():
def __init__(self,graph:List[int:List[int]],graphEdges:List[int:List[int]]):
self.length=len(graph)
self.graph=graph # 邻接表
self.graphEdges=graphEdges # 邻接表同结构的边
self.dfn=[-1]*self.length
self.low=[-1]*self.length
self.bridges=[]
self.timestamp=0
def tarjan(self):
for i in range(self.length):
if self.graph[i]!=-1:
self.tarjanDfs(i,-1)
def tarjanDfs(self,node:int,fatherEdge:int):
self.dfn[node]=self.low[node]=self.timestamp
self.timestamp+=1
for i in range(len(self.graph[node])):
subNode,edge=self.graph[node][i],self.graphEdges[node][i] # 这里边是node指向subNode的边
if self.dfn[subNode]==-1:
self.tarjanDfs(subNode,edge)
self.low[node]=min(self.low[node],self.low[subNode])
if self.dfn[node]<self.low[subNode]:
self.bridges.append(edge)
elif edge!=fatherEdge:
self.low[node]=min(self.low[node],self.low[subNode])
from collections import defaultdict
class Solution:
def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]:
edgesLength=len(edges)
# 将所有的边编号并获取点->边id的映射
for i in range(edgesLength):
edges[i].append(i)
# 将所有的边按权值从小到大进行排序
edges.sort(key=lambda x:x[2])
# 构建所有点的并查集(不用进行连接)
uf=UnionFind()
for i in range(n):
uf.add(i)
# 初始化所有边的标签为-1(1为关键边,0则两者都不是,最后还是-1的为伪关键边)
labels=[-1]*edgesLength
# 遍历所有的相同权值的边组edges[i,j)
i=0
while i<edgesLength:
# 获取j的值
j=i
while j<edgesLength and edges[i][2]==edges[j][2]:
j+=1
# 将各个点按并查集分成m个连通分量,每个连通分量为新图compG中的一个节点,并给每个连通分量初始化需要i
node2CompId={}
compCnt=0
for k in range(i,j):
node1,node2,weight,edgeId=edges[k]
node1Root,node2Root=uf.find(node1),uf.find(node2)
if node1Root!=node2Root:
if node1Root not in node2CompId:
node2CompId[node1Root]=compCnt
compCnt+=1
if node2Root not in node2CompId:
node2CompId[node2Root]=compCnt
compCnt+=1
else:
labels[edgeId]=0 # 改变既不是关键边,也不是伪关键边
# print(node2CompId)
# 构建邻接表形式的compG和对应的边的id
compG=defaultdict(list)
compGEids=defaultdict(list)
for k in range(i,j):
node1,node2,weight,edgeId=edges[k]
node1Root,node2Root=uf.find(node1),uf.find(node2)
if node1Root!=node2Root:
compId1,compId2=node2CompId[node1Root],node2CompId[node2Root]
compG[compId1].append(compId2)
compGEids[compId1].append(edgeId)
compG[compId2].append(compId1)
compGEids[compId2].append(edgeId)
# print(compCnt, compG, compGEids)
ugt2=UndirectedGraphTarjan2(compG, compGEids)
ugt2.tarjan()
bridges=ugt2.bridges
# print("bridges",bridges)
# 将桥标记为1
for bridge in bridges:
labels[bridge]=1
# 遍历[i,j)之间的边。如果该边是compG的桥,则该边是关键边;如果该边首尾连接同一个连通分量节点,则既不是关键边,也不是伪关键边;
for k in range(i,j):
node1,node2,weight,edgeId=edges[k]
uf.union(node1,node2)
# 更新i
i=j
# 标记完成后,剩下的没有标记的边即为伪关键边
# 返回结果
# print(labels)
result=[[],[]]
for i in range(edgesLength):
if labels[i]==1:
result[0].append(i)
elif labels[i]==-1:
result[1].append(i)
return result