第十一章 图论 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 #否则其中一根归于另一根