一、并查集的实现
(就是一个合并建树的过程)
class UnionFind:
def __init__(self, n):
# 初始化每个元素的父节点为自身
self.parent = list(range(n))
def find(self, x):
# 查找元素 x 的根节点
while self.parent[x] != x:
x = self.parent[x]
return x
def union(self, x, y):
# 合并元素 x 和 y 所在的集合
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
self.parent[root_x] = root_y
# 初始化并查集,包含 5 个元素
uf = UnionFind(5)
print("初始状态下各元素的父节点:", uf.parent)
# 合并元素 0 和 1
uf.union(0, 1)
print("合并 0 和 1 后各元素的父节点:", uf.parent)
# 合并元素 2 和 3
uf.union(2, 3)
print("合并 2 和 3 后各元素的父节点:", uf.parent)
# 合并元素 0 和 3
uf.union(0, 3)
print("合并 0 和 3 后各元素的父节点:", uf.parent)
# 查找元素 1 的根节点
root_1 = uf.find(1)
print("元素 1 的根节点是:", root_1)
# 查找元素 4 的根节点
root_4 = uf.find(4)
print("元素 4 的根节点是:", root_4)
结果:
初始状态下各元素的父节点: [0, 1, 2, 3, 4]
合并 0 和 1 后各元素的父节点: [1, 1, 2, 3, 4]
合并 2 和 3 后各元素的父节点: [1, 1, 3, 3, 4]
合并 0 和 3 后各元素的父节点: [1, 3, 3, 3, 4]
元素 1 的根节点是: 3
元素 4 的根节点是: 4
二、带秩和计数的并查集
class UnionFind:
def __init__(self, n):
self.parent = list(range(n)) # 父节点指针
self.rank = [1] * n # 子树高度(初始为1)
self.count = n # 连通分量计数
def find(self, x):
# 路径压缩:将查询路径上的节点直接挂到根节点
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并操作:按秩合并避免树过高
rootX = self.find(x)
rootY = self.find(y)
if rootX == rootY:
return False # 已在同一集合
if self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
self.count -= 1
return True
def is_connected(self, x, y):
return self.find(x) == self.find(y)
n = int(input())
v = [[] for _ in range(n)]
for _ in range(n):
u,k = map(int,input().split())
v[u].append(k)
uf = UnionFind(n)
for i,vv in enumerate(v):
for vvele in vv:
uf.union(i,vvele)
三、举例
# 使用之前提供的模板
edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
uf = UnionFind(5) # 节点编号1-5
for u, v in edges:
print(f"处理边 {u}-{v} 前:", uf.parent[1:])
if not uf.union(u-1, v-1): # 注意调整为0-based索引
print(f"发现冗余边:{u}-{v}")
print(f"处理后:", uf.parent[1:], "\n")
检测无向图环路(如LeetCode 684)
def findRedundantConnection(edges):
uf = UnionFind(len(edges)+1) # 节点编号从1开始
for u, v in edges:
if not uf.union(u, v): # 合并失败说明存在环,即已经连通后再次想要连通,则说明有环
return [u, v]
return []
计算连通分量数(如LeetCode 547)
def findCircleNum(isConnected):
n = len(isConnected) #isConnected是一个n*n的矩阵,1表示i,j之间有连通,0未没有
uf = UnionFind(n)
for i in range(n):
for j in range(i+1, n): #因为无向图是对称矩阵,所以一半就行
if isConnected[i][j]:
uf.union(i, j)
return uf.count # 直接返回连通分量总数
Kruskal最小生成树算法
def kruskal(n, edges):
edges.sort(key=lambda x: x[2]) # 按权重排序
"""
sort 方法:它是 Python 列表对象的一个方法,作用是对列表进行原地排序,
也就是直接改变原列表中元素的顺序。
key=lambda x:x[2]:key 参数用于指定一个函数,这个函数会作用于列表中的每个元素,然后根据函数的返回值来进行排序。
lambda x:x[2] 是一个匿名函数,它接收一个参数 x,并返回 x 的第三个元素(在 Python 里,索引从 0 开始,所以 x[2] 表示第三个元素)。
也就是说,这行代码会按照 edges 列表中每个元素的第三个元素的值来对列表进行排序。
"""
uf = UnionFind(n)
mst = []
for u, v, w in edges:
if uf.union(u, v): # 若两节点未连通(不能有环)
mst.append((u, v, w))
if len(mst) == n-1: # 树边数达标
break
return mst
四、和DFS\BFS对比
并查集 | DFS/BFS | |
---|---|---|
适用场景 | 动态连通性问题 | 静态图遍历 |
时间复杂度 | O(α(n)) 近乎常数 | O(V+E) |
存储方式 | 隐式树结构 | 显式邻接表/矩阵 |
优势操作 | 实时合并与查询 | 路径搜索与拓扑排序 |