并查集(Disjoint Set Union,DSU)是一种用于处理不相交集合的数据结构,主要支持两种操作:查找(Find)和合并(Union)。它在解决连通性问题、图论问题以及动态连通性等问题时非常有用。
基本概念:
核心思想:
时间复杂度:
下面是一个简单的并查集实现,包含路径压缩和按秩合并:
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
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:
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
# 示例用法
uf = UnionFind(10)
uf.union(1, 2)
uf.union(2, 3)
print(uf.find(1) == uf.find(3)) # 输出: True
print(uf.find(1) == uf.find(4)) # 输出: False
在进行代码逐行解释之前,我们先来了解rank的知识:
self.rank
是并查集(Union-Find)中的一个重要概念,它用于优化合并操作(union
),使得并查集的树结构更加平衡,从而提高查找操作(find
)的效率。下面我会详细解释 self.rank
的作用以及它是如何优化并查集的。
在并查集中,秩(Rank) 是用来表示集合的“高度”或“大小”的一个指标。具体来说:
秩的定义:
1
,因为每个集合只有一个元素(根节点)。秩的作用:
在合并两个集合时,我们总是将秩较小的树合并到秩较大的树上。这样做的好处是:
rank[rootX] > rank[rootY]
:
rootY
的父节点设置为 rootX
。rootX
的秩不变。rank[rootX] < rank[rootY]
:
rootX
的父节点设置为 rootY
。rootY
的秩不变。rank[rootX] == rank[rootY]
:
rootY
的父节点设置为 rootX
。rootX
的秩增加 1。假设我们有 4 个元素:0, 1, 2, 3
,初始时每个元素是一个独立的集合。
parent: [0, 1, 2, 3]
rank: [1, 1, 1, 1]
rank[0] == rank[1]
,将 1
的父节点设置为 0
,0
的秩增加 1。parent: [0, 0, 2, 3]
rank: [2, 1, 1, 1]
rank[2] == rank[3]
,将 3
的父节点设置为 2
,2
的秩增加 1。parent: [0, 0, 2, 2]
rank: [2, 1, 2, 1]
rank[0] == rank[2]
,将 2
的父节点设置为 0
,0
的秩增加 1。parent: [0, 0, 0, 2]
rank: [3, 1, 2, 1]
保持树的平衡:
优化查找操作:
动态调整:
两者结合,可以显著提高并查集的效率。
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
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:
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
parent
数组用于存储每个元素的父节点,初始时每个元素的父节点是自己。rank
数组用于存储每个集合的秩(树的高度),初始时为1。好的!这段代码是一个标准的并查集(Union-Find)实现,包含了路径压缩和按秩合并两种优化。我会逐行详细讲解这段代码,并结合并查集的核心知识帮助你彻底理解。
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
self.parent
:
parent[i] = i
。size = 5
,则 parent = [0, 1, 2, 3, 4]
,表示每个元素初始时是一个独立的集合。self.rank
:
size = 5
,则 rank = [1, 1, 1, 1, 1]
。def find(self, x):
# 查找操作,带路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # 路径压缩
return self.parent[x]
功能:
x
所在集合的根节点(代表元素)。逐行解释:
if self.parent[x] != x
:
x
的父节点不是它自己,说明 x
不是根节点。self.parent[x] = self.find(self.parent[x])
:
x
的根节点,并将 x
的父节点直接设置为根节点(路径压缩)。return self.parent[x]
:
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[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
功能:
x
和 y
所在的集合合并。逐行解释:
rootX = self.find(x)
和 rootY = self.find(y)
:
x
和 y
的根节点。if rootX != rootY
:
x
和 y
不在同一个集合中,才需要合并。if self.rank[rootX] > self.rank[rootY]
:
rootX
的秩大于 rootY
的秩,将 rootY
的父节点设置为 rootX
。elif self.rank[rootX] < self.rank[rootY]
:
rootX
的秩小于 rootY
的秩,将 rootX
的父节点设置为 rootY
。else
:
rootX
和 rootY
的秩相等,将 rootY
的父节点设置为 rootX
,并增加 rootX
的秩。按秩合并的作用:
好的!下面是一个完整的并查集模板代码,包含路径压缩和按秩合并优化。同时,我会编写测试代码,用大量数据测试并查集的功能,并详细讲解代码的运行过程和结果。
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
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:
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
def is_connected(self, x, y):
# 判断两个元素是否在同一个集合中
return self.find(x) == self.find(y)
# 测试并查集功能
def test_union_find():
# 初始化并查集,假设有 10 个元素
uf = UnionFind(10)
# 初始状态
print("初始状态:")
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
# 合并操作
print("合并 1 和 2:")
uf.union(1, 2)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 3 和 4:")
uf.union(3, 4)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 1 和 3:")
uf.union(1, 3)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 5 和 6:")
uf.union(5, 6)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 7 和 8:")
uf.union(7, 8)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 5 和 7:")
uf.union(5, 7)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
print("合并 9 和 0:")
uf.union(9, 0)
print("parent:", uf.parent)
print("rank:", uf.rank)
print("----------------------------------")
# 查找操作
print("查找 1 和 4 是否在同一个集合中:", uf.is_connected(1, 4))
print("查找 5 和 8 是否在同一个集合中:", uf.is_connected(5, 8))
print("查找 0 和 9 是否在同一个集合中:", uf.is_connected(0, 9))
print("查找 2 和 6 是否在同一个集合中:", uf.is_connected(2, 6))
print("----------------------------------")
# 最终状态
print("最终状态:")
print("parent:", uf.parent)
print("rank:", uf.rank)
# 运行测试
test_union_find()
初始状态:
parent: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
rank: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
合并 1 和 2:
parent: [0, 1, 1, 3, 4, 5, 6, 7, 8, 9]
rank: [1, 2, 1, 1, 1, 1, 1, 1, 1, 1]
2
的父节点设置为 1
,1
的秩增加为2。合并 3 和 4:
parent: [0, 1, 1, 3, 3, 5, 6, 7, 8, 9]
rank: [1, 2, 1, 2, 1, 1, 1, 1, 1, 1]
4
的父节点设置为 3
,3
的秩增加为2。合并 1 和 3:
parent: [0, 1, 1, 1, 3, 5, 6, 7, 8, 9]
rank: [1, 3, 1, 2, 1, 1, 1, 1, 1, 1]
3
的父节点设置为 1
,1
的秩增加为3。合并 5 和 6:
parent: [0, 1, 1, 1, 3, 5, 5, 7, 8, 9]
rank: [1, 3, 1, 2, 1, 2, 1, 1, 1, 1]
6
的父节点设置为 5
,5
的秩增加为2。合并 7 和 8:
parent: [0, 1, 1, 1, 3, 5, 5, 7, 7, 9]
rank: [1, 3, 1, 2, 1, 2, 1, 2, 1, 1]
8
的父节点设置为 7
,7
的秩增加为2。合并 5 和 7:
parent: [0, 1, 1, 1, 3, 5, 5, 5, 7, 9]
rank: [1, 3, 1, 2, 1, 3, 1, 2, 1, 1]
7
的父节点设置为 5
,5
的秩增加为3。合并 9 和 0:
parent: [0, 1, 1, 1, 3, 5, 5, 5, 7, 0]
rank: [2, 3, 1, 2, 1, 3, 1, 2, 1, 1]
9
的父节点设置为 0
,0
的秩增加为2。查找 1 和 4 是否在同一个集合中: True
查找 5 和 8 是否在同一个集合中: True
查找 0 和 9 是否在同一个集合中: True
查找 2 和 6 是否在同一个集合中: False
1
和 4
在同一个集合中(根节点都是 1
)。5
和 8
在同一个集合中(根节点都是 5
)。0
和 9
在同一个集合中(根节点都是 0
)。2
和 6
不在同一个集合中(根节点分别是 1
和 5
)。最终状态:
parent: [0, 1, 1, 1, 3, 5, 5, 5, 7, 0]
rank: [2, 3, 1, 2, 1, 3, 1, 2, 1, 1]
通过这段代码和测试,你可以清晰地看到并查集的工作过程:
希望这段详细的讲解和测试代码能帮助你彻底理解并查集!如果还有疑问,欢迎继续提问!
集合的表示:
查找操作(Find):
合并操作(Union):
假设我们有 5 个元素:0, 1, 2, 3, 4
,初始时每个元素是一个独立的集合。
parent = [0, 1, 2, 3, 4]
rank = [1, 1, 1, 1, 1]
union(0, 1)
0
的根节点是 0
,1
的根节点是 1
。rank[0] == rank[1]
,将 1
的父节点设置为 0
,并增加 rank[0]
。parent = [0, 0, 2, 3, 4]
rank = [2, 1, 1, 1, 1]
union(2, 3)
2
的根节点是 2
,3
的根节点是 3
。rank[2] == rank[3]
,将 3
的父节点设置为 2
,并增加 rank[2]
。parent = [0, 0, 2, 2, 4]
rank = [2, 1, 2, 1, 1]
union(1, 3)
1
的根节点是 0
,3
的根节点是 2
。rank[0] == rank[2]
,将 2
的父节点设置为 0
,并增加 rank[0]
。parent = [0, 0, 0, 2, 4]
rank = [3, 1, 2, 1, 1]
find(3)
3
开始,找到父节点 2
。2
开始,找到父节点 0
。0
,并进行路径压缩,将 3
的父节点直接设置为 0
。parent = [0, 0, 0, 0, 4]
rank = [3, 1, 2, 1, 1]
这段代码是一个标准的并查集实现,包含了路径压缩和按秩合并两种优化。通过逐行讲解和示例演示,希望你能够彻底理解并查集的工作原理和代码实现!如果还有疑问,欢迎继续提问!
好的!我会从头开始详细解释并查集(Union-Find)数据结构,并结合这道题目的具体解法,帮助你彻底理解并查集以及如何用它解决这类问题。
并查集(Union-Find)是一种用于管理不相交集合的数据结构。它支持以下两种核心操作:
并查集常用于解决动态连通性问题,比如判断两个元素是否属于同一个集合,或者将两个集合合并。
集合的表示:
查找操作(Find):
合并操作(Union):
题目给出了一组等式和不等式,要求判断是否存在一种赋值方式,使得所有等式和不等式都成立。例如:
a==b
表示 a
和 b
必须相等。a!=b
表示 a
和 b
必须不相等。等式处理:
a==b
表示 a
和 b
属于同一个集合。union
操作将 a
和 b
合并到同一个集合中。不等式处理:
a!=b
表示 a
和 b
必须属于不同的集合。find
操作检查 a
和 b
是否在同一个集合中。如果在同一个集合中,则说明矛盾,返回 False
。步骤总结:
False
。True
。class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
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:
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
class Solution:
def equationsPossible(self, equations: List[str]) -> bool:
# 初始化并查集,26个小写字母
uf = UnionFind(26)
# 首先处理所有等式,将等号两边的字母合并
for eq in equations:
if eq[1] == '=':
x = ord(eq[0]) - ord('a') # 将字母映射到0-25
y = ord(eq[3]) - ord('a')
uf.union(x, y)
# 然后处理所有不等式,检查是否矛盾
for eq in equations:
if eq[1] == '!':
x = ord(eq[0]) - ord('a')
y = ord(eq[3]) - ord('a')
if uf.find(x) == uf.find(y): # 如果x和y在同一个集合中,则矛盾
return False
return True
UnionFind类:
parent
数组:存储每个节点的父节点。初始时,每个节点的父节点是它自己。rank
数组:存储每个集合的秩(树的高度)。初始时,每个集合的秩为1。find
方法:查找某个节点的根节点,并在查找过程中进行路径压缩。union
方法:合并两个集合,并根据秩的大小决定如何合并。equationsPossible方法:
==
),将等号两边的字母合并。!=
),检查不等号两边的字母是否在同一个集合中。如果在同一个集合中,则返回 False
。True
。输入:["a==b","b!=a"]
parent = [0, 1, 2, ..., 25]
(每个字母初始时属于自己单独的集合)。a==b
:
a
和 b
合并,parent[1] = 0
(假设 a
的索引是0,b
的索引是1)。b!=a
:
a
和 b
是否在同一个集合中。发现它们在同一个集合中,返回 False
。输入:["b==a","a==b"]
b==a
:
a
和 b
合并。a==b
:
a
和 b
,发现它们已经在同一个集合中。True
。好的!下面我会为这段代码编写详细的测试代码,并逐步打印出每一步的运行结果。同时,我会结合代码的每一行,详细说明其作用和实现原理。
并查集是一种用于管理不相交集合的数据结构,支持以下两种核心操作:
在本题中,并查集用于维护变量之间的等价关系:
a==b
表示 a
和 b
属于同一个集合。a!=b
表示 a
和 b
必须属于不同的集合。self.parent
:存储每个元素的父节点,初始时每个元素的父节点是它自己。self.rank
:存储每个集合的秩(树的高度),初始时为1。False
。class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个元素的父节点为自己
self.rank = [1] * size # 初始化每个集合的秩为1
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:
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
class Solution:
def equationsPossible(self, equations: List[str]) -> bool:
# 初始化并查集,26个小写字母
uf = UnionFind(26)
# 首先处理所有等式,将等号两边的变量合并
print("处理等式:")
for eq in equations:
if eq[1] == '=':
x = ord(eq[0]) - ord('a')
y = ord(eq[3]) - ord('a')
print(f"合并 {eq[0]} 和 {eq[3]}")
uf.union(x, y)
print(f"parent: {uf.parent}")
print(f"rank: {uf.rank}")
print("-----------------------------")
# 然后处理所有不等式,检查是否矛盾
print("处理不等式:")
for eq in equations:
if eq[1] == '!':
x = ord(eq[0]) - ord('a')
y = ord(eq[3]) - ord('a')
print(f"检查 {eq[0]} 和 {eq[3]} 是否在同一个集合中")
if uf.find(x) == uf.find(y):
print(f"矛盾:{eq[0]} 和 {eq[3]} 在同一个集合中")
return False
else:
print(f"无矛盾:{eq[0]} 和 {eq[3]} 不在同一个集合中")
print("-----------------------------")
return True
# 测试代码
def test_equationsPossible():
equations = ["a==b", "b!=a"] # 测试用例
solution = Solution()
result = solution.equationsPossible(equations)
print("最终结果:", result)
# 运行测试
test_equationsPossible()
equations = ["a==b", "b!=a"]
初始化并查集:
parent = [0, 1, 2, ..., 25]
(每个字母初始时属于自己单独的集合)。rank = [1, 1, 1, ..., 1]
(每个集合的秩为1)。处理等式 a==b
:
a
和 b
合并。a
的索引是 0
,b
的索引是 1
。parent = [0, 0, 2, ..., 25]
,rank = [2, 1, 1, ..., 1]
。处理不等式 b!=a
:
a
和 b
是否在同一个集合中。a
和 b
在同一个集合中(根节点都是 0
),返回 False
。处理等式:
合并 a 和 b
parent: [0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
rank: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
-----------------------------
处理不等式:
检查 b 和 a 是否在同一个集合中
矛盾:b 和 a 在同一个集合中
最终结果: False
__init__
:
parent
和 rank
,每个元素初始时属于自己单独的集合。find
:
union
:
equationsPossible
:
False
。True
。