算法-并查集
0. 写在前面的小结
并查集的功能:
- 将两个元素添加到一个集合中
join(node1, node2)
- 判断两个元素是否在同一个集合、连通分量中
find(node1) == find(node2)
基本思想:
- 每个集合、连通分量都通过一个元素(根节点)来代表。
- 通过维护
father[]
数组来实现 - 路径压缩:在
find()
的过程中,如果处于第三层,可以将3的祖先改为1,从而降低了树的高度。 - 路径压缩后的
find()
的时间复杂度从O(logn)
变为O(1)
1 路径压缩 1
2 -------> 2 3
3
1. 寻找图中是否存在路径(LeetCode 1971)
有一个具有 n 个顶点的双向图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。
图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。
每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点source
开始,到顶点destination
结束的有效路径
。
如果存在有效路径返回 true,否则返回 false 。
# 并查集
class UnionFindSet:
# 初始化并查集
def __init__(self, n):
self.father = list(range(n))
# 寻找本联通分量的根
def find(self, node1:int) -> int:
if node1 == self.father[node1]:
return node1
else:
# 路径压缩
self.father[node1] = self.find(self.father[node1])
return self.father[node1]
def join(self, node1: int, node2: int):
root1 = self.find(node1)
root2 = self.find(node2)
if root1 == root2:
return
if root1 != root2:
self.father[root2] = root1
class Solution:
def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
ufs = UnionFindSet(n)
for x, y in edges:
ufs.join(x, y)
return ufs.find(source) == ufs.find(destination)
2. 冗余连接 (LeetCode 684)
树可以看成是一个连通且无环的无向图。
给定往一棵n 个节点 (节点值 1~n) 的树中添加一条边
后的图。
添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。
图的信息记录于长度为n的二维数组edges,edges[i] = [ai, bi]
表示图中在ai
和bi
之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着n个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。
# 并查集
class UnionFindSet:
# 初始化并查集
def __init__(self, n):
self.father = list(range(n))
# 寻找本联通分量的根
def find(self, node1:int) -> int:
if node1 == self.father[node1]:
return node1
else:
# 路径压缩
self.father[node1] = self.find(self.father[node1])
return self.father[node1]
def join(self, node1: int, node2: int):
root1 = self.find(node1)
root2 = self.find(node2)
if root1 == root2:
return
if root1 != root2:
self.father[root2] = root1
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
ufs = UnionFindSet(len(edges)+1)
for x, y in edges:
# 已经连通了,还要添加的边为冗余边
if ufs.find(x) == ufs.find(y):
return [x,y]
else:
ufs.join(x, y)
3. 冗余连接II (LeetCode 685)(有难度)
有三种情况:
- 存在两条入度为2的边,且删除任一条,剩下来的都构成树,那么
删除后面那条
- 存在两条入度为2的边,必须删除指定的一条,才能构成树
- 存在有向环,此类情况同
冗余连接 I
# 并查集
class UnionFindSet:
# 初始化并查集
def __init__(self, n):
self.father = list(range(n))
# 寻找本联通分量的根
def find(self, node1:int) -> int:
if node1 != self.father[node1]:
self.father[node1] = self.find(self.father[node1])
return self.father[node1]
def join(self, node1: int, node2: int):
root1 = self.find(node1)
root2 = self.find(node2)
if root1 != root2:
self.father[root2] = root1
class Solution:
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
# 使用并查集,判断删一条边之后是否为树
def isTreeAfterRemoveEdge(edges, deleteEdge):
# 创建并查集
ufs = UnionFindSet(len(edges) + 1)
for x, y in edges:
if [x, y] == deleteEdge:
continue
if ufs.find(x) == ufs.find(y):
return False
ufs.join(x, y)
return True
# 使用并查集,移除有向环中的冗余边
def removeCycleEdge(edges):
# 创建并查集
ufs = UnionFindSet(len(edges) + 1)
for x, y in edges:
if ufs.find(x) == ufs.find(y):
return [x, y]
ufs.join(x, y)
return None
# 记录每个顶点的入度
indegrees = [0] * (len(edges) + 1)
for x, y in edges:
indegrees[y] += 1
# 记录入度为2的顶点关联的边(最多2条)
edgeStore = []
# n-1 ~ 0
for i in range(len(edges)-1, -1, -1):
if indegrees[edges[i][1]] == 2:
edgeStore.append(edges[i])
# 情况1,2:存在两条入度为2的边
if edgeStore:
# 优先删排在后面的边
if isTreeAfterRemoveEdge(edges, edgeStore[0]):
return edgeStore[0]
else:
return edgeStore[1]
# 情况3:没有入度为2的边,存在有向环
return removeCycleEdge(edges)