并查集(Disjoint Set Union)是一种高效处理不相交集合合并与查询问题的数据结构,广泛应用于连通性问题、动态联盟问题等多种场景。本文将详细介绍并查集的基本概念、应用场景、实现方法以及优化技巧,并通过实例进一步说明其在实际问题中的应用。
并查集简介并查集的基本概念
并查集(Disjoint Set Union,简称 DSU)是一种用于处理不相交集合的合并与查询问题的数据结构。它由一组动态集合构成,每个集合中的元素互不相同,并且每个集合都有一个唯一的代表元素。并查集有两种基本操作:合并(Union)和查找(Find)。
- 合并:将两个不同的集合合并成一个集合。
- 查找:查找某元素所在的集合。
并查集的重要特性是支持在几乎常数时间内完成合并和查找操作。并查集通常用于解决连通性问题、动态联盟问题等。
并查集的应用场景
并查集在很多场景中都有应用,以下是几种典型的应用场景:
- 连通性问题:例如,确定一个图中各个连通分量的大小,或者在一个无向图中判断两点是否连通。
- 动态联盟问题:例如,模拟社交网络中的用户关系,或者在团队协作中检查用户是否属于同一个分组。
- 路径压缩:通过路径压缩技术优化查询操作,使得查找操作的时间复杂度接近于常数。
- 按秩合并:通过增加额外的逻辑,使得合并操作更加高效。
并查集的数据结构
并查集通常使用数组或哈希表来实现。数组形式通常用于元素的索引与元素本身一一对应的情况;哈希表则适用于元素的索引与元素不一一对应的情况。这里以数组实现为例。
数据结构通常包含以下两个部分:
parent
数组:用于存储每个元素的父节点。parent[i]
表示元素i
的父节点。根节点(即没有父节点)自身的parent[i] == i
。rank
数组(可选):用于维护每个节点的树的深度,用于按秩合并优化。
class DisjointSet:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n # Optional: for path compression and union by rank
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
并查集的基本操作(查找、合并)
-
查找操作(
find
):查找某个元素所在的根节点。def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) # Path compression return self.parent[x]
-
合并操作(
union
):将两个集合合并为一个集合。def union(self, x, y): rootX = self.find(x) rootY = self.find(y) if rootX != rootY: if self.rank[rootX] < self.rank[rootY]: self.parent[rootX] = rootY elif self.rank[rootX] > self.rank[rootY]: self.parent[rootY] = rootX else: self.parent[rootY] = rootX self.rank[rootX] += 1
路径压缩
路径压缩是一种优化方法,用于在查找操作中压缩树的高度,使得查找操作的复杂度接近于常数。具体来说,当查找某个节点时,会将该节点到根节点路径上的所有节点的父节点直接指向根节点。
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
按秩合并
按秩合并是一种优化方法,用于在合并操作中减少树的高度。具体来说,合并时总是将较小树的根节点指向较大的树的根节点,这样可以保证合并后的树深度不会增加太多。
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
并查集的实例应用
并查集解决连通性问题
给定一个无向图,判断两点是否连通。可以使用并查集来解决这个问题。
def isConnected(graph, src, dest):
ds = DisjointSet(len(graph))
for u in range(len(graph)):
for v in graph[u]:
ds.union(u, v)
return ds.find(src) == ds.find(dest)
并查集解决动态联盟问题
动态联盟问题,例如在社交网络中,模拟用户之间的联盟关系。可以使用并查集维护各个联盟,并在需要时合并或查询联盟。
def mergeAlliances(people, alliances):
ds = DisjointSet(len(people))
for alliance in alliances:
for person in alliance[1:]:
ds.union(alliance[0], person)
# Check if two people belong to the same alliance
for personA, personB in pairs:
if ds.find(personA) == ds.find(personB):
print(f"{personA} and {personB} are in the same alliance.")
else:
print(f"{personA} and {personB} are not in the same alliance.")
并查集的进阶使用
并查集与哈希表的结合
在某些应用场景中,节点的索引和节点本身可能不一一对应。此时,可以使用哈希表来存储节点与其索引的映射关系。
class DisjointSetWithHash:
def __init__(self):
self.parent = {}
self.rank = {}
def find(self, x):
if x not in self.parent:
self.parent[x] = x
self.rank[x] = 0
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
并查集的变种应用
并查集还可以用于解决其他问题,例如判断一个图是否存在环。
def hasCycle(graph):
ds = DisjointSet(len(graph))
for u in range(len(graph)):
for v in graph[u]:
rootU = ds.find(u)
rootV = ds.find(v)
if rootU == rootV:
return True
ds.union(rootU, rootV)
return False
总结与练习
并查集常见问题解答
-
并查集的时间复杂度是多少?
并查集的时间复杂度在路径压缩和按秩合并的优化下,接近于常数时间,即O(1)。 -
并查集适合处理什么类型的问题?
并查集适合处理不相交集合的合并与查询问题,如图的连通性问题、动态联盟问题等。 - 路径压缩和按秩合并有什么区别?
路径压缩优化的是查找操作,使得查找操作的时间复杂度接近于常数;按秩合并优化的是合并操作,使得合并操作的树深度不会增加太多。
并查集相关练习题推荐
推荐一些并查集相关练习题,帮助你更好地理解和应用并查集。
-
连通性问题
- 给定一个无向图,判断每个点是否在一个连通分量中。
- 给定一个图,判断图中是否存在环。
-
动态联盟问题
- 在社交网络中,模拟用户之间的联盟关系,判断是否存在公共联盟。
- 在团队协作中,检查用户是否属于同一个分组。
- 其他应用
- 在图论中,使用并查集解决最小生成树问题。
- 在字符串匹配中,使用并查集优化 KMP 算法。
通过解决这些练习题,可以帮助你更好地掌握并查集的使用技巧和应用场景。可以参考MooC网上的相关课程进行学习和练习。
共同学习,写下你的评论
评论加载中...
作者其他优质文章