当前位置: 首页 > news >正文

【并查集】

并查集(Disjoint Set Union,DSU)是一种用于处理不相交集合的数据结构,主要支持两种操作:查找(Find)和合并(Union)。它在解决连通性问题、图论问题以及动态连通性等问题时非常有用。

并查集的基础知识

  1. 基本概念

    • 集合:并查集维护一组不相交的集合,每个集合有一个代表元素。
    • 查找(Find):查找某个元素所属的集合的代表元素。
    • 合并(Union):将两个集合合并为一个集合。
  2. 核心思想

    • 路径压缩:在查找操作中,将查找路径上的所有节点直接连接到根节点,以加快后续查找操作。
    • 按秩合并:在合并操作中,将较小的树连接到较大的树的根节点上,以保持树的平衡。
  3. 时间复杂度

    • 查找(Find):接近O(1)(由于路径压缩)。
    • 合并(Union):接近O(1)(由于按秩合并)。

Python实现

下面是一个简单的并查集实现,包含路径压缩和按秩合并:

class UnionFind:def __init__(self, size):self.parent = list(range(size))  # 初始化每个元素的父节点为自己self.rank = [1] * size  # 初始化每个集合的秩为1def 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] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.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)?

在并查集中,秩(Rank) 是用来表示集合的“高度”或“大小”的一个指标。具体来说:

  • 秩的定义

    • 对于每个集合(树),秩表示该集合的树的高度(从根节点到最远叶子节点的路径长度)。
    • 初始时,每个集合的秩为 1,因为每个集合只有一个元素(根节点)。
  • 秩的作用

    • 在合并两个集合时,通过比较两个集合的秩,决定如何合并,从而保持树的平衡。
    • 秩越高,表示该集合的树越“深”或“大”,因此应该将较小的树合并到较大的树上。

秩的设定与优化

1. 按秩合并(Union by Rank)

在合并两个集合时,我们总是将秩较小的树合并到秩较大的树上。这样做的好处是:

  • 避免树的高度过高,从而保证查找操作的时间复杂度接近 O(1)。
  • 如果两棵树的秩相同,则合并后秩会增加 1。
2. 秩的更新规则
  • 如果 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]
操作 1:合并 0 和 1
  • rank[0] == rank[1],将 1 的父节点设置为 00 的秩增加 1。
parent: [0, 0, 2, 3]
rank:   [2, 1, 1, 1]
操作 2:合并 2 和 3
  • rank[2] == rank[3],将 3 的父节点设置为 22 的秩增加 1。
parent: [0, 0, 2, 2]
rank:   [2, 1, 2, 1]
操作 3:合并 1 和 3
  • rank[0] == rank[2],将 2 的父节点设置为 00 的秩增加 1。
parent: [0, 0, 0, 2]
rank:   [3, 1, 2, 1]

秩的作用总结

  1. 保持树的平衡

    • 通过按秩合并,避免树的高度过高,从而保证查找操作的时间复杂度接近 O(1)。
  2. 优化查找操作

    • 树的高度越低,查找操作的路径越短,路径压缩的效果也越好。
  3. 动态调整

    • 秩会根据合并操作动态调整,始终保持树的平衡。

秩与路径压缩的关系

  • 路径压缩
    • 在查找操作中,将路径上的所有节点直接连接到根节点,从而降低树的高度。
  • 秩的作用
    • 在合并操作中,通过按秩合并,避免树的高度过高,从而为路径压缩提供更好的基础。

两者结合,可以显著提高并查集的效率。


代码示例

class UnionFind:def __init__(self, size):self.parent = list(range(size))  # 初始化每个元素的父节点为自己self.rank = [1] * size  # 初始化每个集合的秩为1def 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] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.rank[rootX] += 1

总结

  • 秩的作用:通过按秩合并,保持树的平衡,从而优化查找操作。
  • 秩的更新规则:在合并时,将较小的树合并到较大的树上,如果秩相同,则增加秩。
  • 秩与路径压缩的关系:秩的优化为路径压缩提供了更好的基础,两者结合可以显著提高并查集的效率。

代码解释

  • 初始化parent数组用于存储每个元素的父节点,初始时每个元素的父节点是自己。rank数组用于存储每个集合的秩(树的高度),初始时为1。
  • 查找(Find):通过递归查找元素的根节点,并在查找过程中进行路径压缩,使得后续查找操作更快。
  • 合并(Union):找到两个元素的根节点,如果根节点不同,则将它们合并。合并时根据秩的大小决定如何合并,以保持树的平衡。

应用场景

  • 图的连通性问题:判断图中两个节点是否连通。
  • 动态连通性问题:在一系列连接操作中,动态判断两个元素是否连通。
  • 最小生成树算法:如Kruskal算法中用于判断是否形成环。

并查集代码详细解释

好的!这段代码是一个标准的并查集(Union-Find)实现,包含了路径压缩按秩合并两种优化。我会逐行详细讲解这段代码,并结合并查集的核心知识帮助你彻底理解。


代码逐行讲解

1. 初始化部分
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

    • 这是一个列表,用于存储每个集合的秩(树的高度)。
    • 初始时,每个集合的秩为1,因为每个集合只有它自己一个元素。
    • 例如,如果 size = 5,则 rank = [1, 1, 1, 1, 1]

2. 查找操作(Find)
def find(self, x):# 查找操作,带路径压缩if self.parent[x] != x:self.parent[x] = self.find(self.parent[x])  # 路径压缩return self.parent[x]
  • 功能

    • 查找元素 x 所在集合的根节点(代表元素)。
    • 在查找过程中,进行路径压缩,将查找路径上的所有节点直接连接到根节点,以加快后续查找操作。
  • 逐行解释

    1. if self.parent[x] != x
      • 如果 x 的父节点不是它自己,说明 x 不是根节点。
    2. self.parent[x] = self.find(self.parent[x])
      • 递归查找 x 的根节点,并将 x 的父节点直接设置为根节点(路径压缩)。
    3. return self.parent[x]
      • 返回 x 的根节点。
  • 路径压缩的作用

    • 将查找路径上的所有节点直接连接到根节点,使得后续查找操作的时间复杂度接近 O(1)。

3. 合并操作(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[rootY] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.rank[rootX] += 1
  • 功能

    • 将元素 xy 所在的集合合并。
    • 在合并过程中,使用按秩合并,将较小的树合并到较大的树上,以保持树的平衡。
  • 逐行解释

    1. rootX = self.find(x)rootY = self.find(y)
      • 找到 xy 的根节点。
    2. if rootX != rootY
      • 如果 xy 不在同一个集合中,才需要合并。
    3. if self.rank[rootX] > self.rank[rootY]
      • 如果 rootX 的秩大于 rootY 的秩,将 rootY 的父节点设置为 rootX
    4. elif self.rank[rootX] < self.rank[rootY]
      • 如果 rootX 的秩小于 rootY 的秩,将 rootX 的父节点设置为 rootY
    5. else
      • 如果 rootXrootY 的秩相等,将 rootY 的父节点设置为 rootX,并增加 rootX 的秩。
  • 按秩合并的作用

    • 保持树的平衡,避免树的高度过高,从而保证查找操作的时间复杂度接近 O(1)。

好的!下面是一个完整的并查集模板代码,包含路径压缩和按秩合并优化。同时,我会编写测试代码,用大量数据测试并查集的功能,并详细讲解代码的运行过程和结果。


完整的并查集模板代码

class UnionFind:def __init__(self, size):self.parent = list(range(size))  # 初始化每个元素的父节点为自己self.rank = [1] * size  # 初始化每个集合的秩为1def 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] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.rank[rootX] += 1def 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。

合并 1 和 2
合并 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 的父节点设置为 11 的秩增加为2。

合并 3 和 4
合并 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 的父节点设置为 33 的秩增加为2。

合并 1 和 3
合并 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 的父节点设置为 11 的秩增加为3。

合并 5 和 6
合并 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 的父节点设置为 55 的秩增加为2。

合并 7 和 8
合并 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 的父节点设置为 77 的秩增加为2。

合并 5 和 7
合并 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 的父节点设置为 55 的秩增加为3。

合并 9 和 0
合并 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 的父节点设置为 00 的秩增加为2。

查找操作
查找 1 和 4 是否在同一个集合中: True
查找 5 和 8 是否在同一个集合中: True
查找 0 和 9 是否在同一个集合中: True
查找 2 和 6 是否在同一个集合中: False
  • 14 在同一个集合中(根节点都是 1)。
  • 58 在同一个集合中(根节点都是 5)。
  • 09 在同一个集合中(根节点都是 0)。
  • 26 不在同一个集合中(根节点分别是 15)。

最终状态
最终状态:
parent: [0, 1, 1, 1, 3, 5, 5, 5, 7, 0]
rank: [2, 3, 1, 2, 1, 3, 1, 2, 1, 1]
  • 最终并查集的状态反映了所有合并操作的结果。

总结

通过这段代码和测试,你可以清晰地看到并查集的工作过程:

  1. 初始化:每个元素是一个独立的集合。
  2. 合并操作:将两个集合合并为一个集合,同时更新秩。
  3. 查找操作:判断两个元素是否在同一个集合中,同时进行路径压缩。

希望这段详细的讲解和测试代码能帮助你彻底理解并查集!如果还有疑问,欢迎继续提问!

并查集的核心知识回顾

  1. 集合的表示

    • 每个集合用一棵树表示,树的根节点是集合的“代表元素”。
    • 每个节点都有一个父节点,根节点的父节点是它自己。
  2. 查找操作(Find)

    • 查找某个元素所在集合的根节点。
    • 使用路径压缩优化,将查找路径上的所有节点直接连接到根节点。
  3. 合并操作(Union)

    • 将两个集合合并为一个集合。
    • 使用按秩合并优化,将较小的树合并到较大的树上。

示例演示

假设我们有 5 个元素:0, 1, 2, 3, 4,初始时每个元素是一个独立的集合。

初始化
parent = [0, 1, 2, 3, 4]
rank = [1, 1, 1, 1, 1]
操作 1:合并 0 和 1
union(0, 1)
  • 找到 0 的根节点是 01 的根节点是 1
  • 因为 rank[0] == rank[1],将 1 的父节点设置为 0,并增加 rank[0]
parent = [0, 0, 2, 3, 4]
rank = [2, 1, 1, 1, 1]
操作 2:合并 2 和 3
union(2, 3)
  • 找到 2 的根节点是 23 的根节点是 3
  • 因为 rank[2] == rank[3],将 3 的父节点设置为 2,并增加 rank[2]
parent = [0, 0, 2, 2, 4]
rank = [2, 1, 2, 1, 1]
操作 3:合并 1 和 3
union(1, 3)
  • 找到 1 的根节点是 03 的根节点是 2
  • 因为 rank[0] == rank[2],将 2 的父节点设置为 0,并增加 rank[0]
parent = [0, 0, 0, 2, 4]
rank = [3, 1, 2, 1, 1]
操作 4:查找 3 的根节点
find(3)
  • 3 开始,找到父节点 2
  • 2 开始,找到父节点 0
  • 返回根节点 0,并进行路径压缩,将 3 的父节点直接设置为 0
parent = [0, 0, 0, 0, 4]
rank = [3, 1, 2, 1, 1]

总结

这段代码是一个标准的并查集实现,包含了路径压缩和按秩合并两种优化。通过逐行讲解和示例演示,希望你能够彻底理解并查集的工作原理和代码实现!如果还有疑问,欢迎继续提问!

990.等式方程的可能性

好的!我会从头开始详细解释并查集(Union-Find)数据结构,并结合这道题目的具体解法,帮助你彻底理解并查集以及如何用它解决这类问题。


什么是并查集?

并查集(Union-Find)是一种用于管理不相交集合的数据结构。它支持以下两种核心操作:

  1. 查找(Find):查找某个元素所属的集合(通常用集合的“代表元素”表示)。
  2. 合并(Union):将两个集合合并为一个集合。

并查集常用于解决动态连通性问题,比如判断两个元素是否属于同一个集合,或者将两个集合合并。


并查集的核心思想

  1. 集合的表示

    • 每个集合用一棵树表示,树的根节点是集合的“代表元素”。
    • 每个节点都有一个父节点,根节点的父节点是它自己。
  2. 查找操作(Find)

    • 从某个节点出发,沿着父节点向上查找,直到找到根节点。
    • 为了提高效率,可以使用路径压缩:在查找过程中,将路径上的所有节点直接连接到根节点。
  3. 合并操作(Union)

    • 找到两个元素所在集合的根节点。
    • 将其中一个根节点的父节点设置为另一个根节点。
    • 为了提高效率,可以使用按秩合并:将较小的树合并到较大的树上,以保持树的平衡。

并查集在本题中的应用

题目分析

题目给出了一组等式和不等式,要求判断是否存在一种赋值方式,使得所有等式和不等式都成立。例如:

  • 等式 a==b 表示 ab 必须相等。
  • 不等式 a!=b 表示 ab 必须不相等。
解题思路
  1. 等式处理

    • 等式 a==b 表示 ab 属于同一个集合。
    • 我们可以用并查集的 union 操作将 ab 合并到同一个集合中。
  2. 不等式处理

    • 不等式 a!=b 表示 ab 必须属于不同的集合。
    • 我们可以用并查集的 find 操作检查 ab 是否在同一个集合中。如果在同一个集合中,则说明矛盾,返回 False
  3. 步骤总结

    • 初始化并查集,每个字母初始时属于自己单独的集合。
    • 遍历所有等式,将等号两边的字母合并。
    • 遍历所有不等式,检查不等号两边的字母是否在同一个集合中。如果在同一个集合中,则返回 False
    • 如果所有不等式都满足,则返回 True

代码实现

class UnionFind:def __init__(self, size):self.parent = list(range(size))  # 初始化每个元素的父节点为自己self.rank = [1] * size  # 初始化每个集合的秩为1def 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] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.rank[rootX] += 1class 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-25y = 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 Falsereturn True

代码逐行解释

  1. UnionFind类

    • parent数组:存储每个节点的父节点。初始时,每个节点的父节点是它自己。
    • rank数组:存储每个集合的秩(树的高度)。初始时,每个集合的秩为1。
    • find方法:查找某个节点的根节点,并在查找过程中进行路径压缩。
    • union方法:合并两个集合,并根据秩的大小决定如何合并。
  2. equationsPossible方法

    • 初始化并查集,26个小写字母对应0到25的索引。
    • 遍历所有等式:
      • 如果是等式(==),将等号两边的字母合并。
    • 遍历所有不等式:
      • 如果是不等式(!=),检查不等号两边的字母是否在同一个集合中。如果在同一个集合中,则返回 False
    • 如果所有不等式都满足,则返回 True

示例演示

示例 1:

输入:["a==b","b!=a"]

  1. 初始化并查集:
    • parent = [0, 1, 2, ..., 25](每个字母初始时属于自己单独的集合)。
  2. 处理等式 a==b
    • ab 合并,parent[1] = 0(假设 a 的索引是0,b 的索引是1)。
  3. 处理不等式 b!=a
    • 检查 ab 是否在同一个集合中。发现它们在同一个集合中,返回 False
示例 2:

输入:["b==a","a==b"]

  1. 初始化并查集。
  2. 处理等式 b==a
    • ab 合并。
  3. 处理等式 a==b
    • 再次检查 ab,发现它们已经在同一个集合中。
  4. 没有不等式,返回 True

总结

  • 并查集的核心是查找合并操作。
  • 在本题中,我们通过并查集维护字母之间的等价关系,并通过检查不等式来判断是否存在矛盾。
  • 路径压缩和按秩合并是并查集的优化技巧,可以显著提高效率。

测试代码与详解

好的!下面我会为这段代码编写详细的测试代码,并逐步打印出每一步的运行结果。同时,我会结合代码的每一行,详细说明其作用和实现原理。


代码的作用与实现原理

1. 并查集(Union-Find)的作用

并查集是一种用于管理不相交集合的数据结构,支持以下两种核心操作:

  • 查找(Find):查找某个元素所属的集合(通常用集合的“代表元素”表示)。
  • 合并(Union):将两个集合合并为一个集合。

在本题中,并查集用于维护变量之间的等价关系:

  • 等式 a==b 表示 ab 属于同一个集合。
  • 不等式 a!=b 表示 ab 必须属于不同的集合。
2. 代码的实现原理
  • 初始化
    • self.parent:存储每个元素的父节点,初始时每个元素的父节点是它自己。
    • self.rank:存储每个集合的秩(树的高度),初始时为1。
  • 查找操作(Find)
    • 查找某个元素的根节点,并在查找过程中进行路径压缩,将路径上的所有节点直接连接到根节点。
  • 合并操作(Union)
    • 找到两个元素的根节点,将较小的树合并到较大的树上,以保持树的平衡。
  • 等式方程的处理
    • 遍历所有等式,将等号两边的变量合并。
    • 遍历所有不等式,检查不等号两边的变量是否在同一个集合中。如果在同一个集合中,则返回 False

测试代码

class UnionFind:def __init__(self, size):self.parent = list(range(size))  # 初始化每个元素的父节点为自己self.rank = [1] * size  # 初始化每个集合的秩为1def 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] = rootXelif self.rank[rootX] < self.rank[rootY]:self.parent[rootX] = rootYelse:self.parent[rootY] = rootXself.rank[rootX] += 1class 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 Falseelse: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"]
运行过程
  1. 初始化并查集

    • parent = [0, 1, 2, ..., 25](每个字母初始时属于自己单独的集合)。
    • rank = [1, 1, 1, ..., 1](每个集合的秩为1)。
  2. 处理等式 a==b

    • ab 合并。
    • a 的索引是 0b 的索引是 1
    • 合并后,parent = [0, 0, 2, ..., 25]rank = [2, 1, 1, ..., 1]
  3. 处理不等式 b!=a

    • 检查 ab 是否在同一个集合中。
    • 发现 ab 在同一个集合中(根节点都是 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

代码逐行解释

1. UnionFind类
  • __init__
    • 初始化 parentrank,每个元素初始时属于自己单独的集合。
  • find
    • 查找某个元素的根节点,并在查找过程中进行路径压缩。
  • union
    • 合并两个集合,并根据秩的大小决定如何合并。
2. Solution类
  • equationsPossible
    • 初始化并查集,26个小写字母对应0到25的索引。
    • 遍历所有等式,将等号两边的变量合并。
    • 遍历所有不等式,检查不等号两边的变量是否在同一个集合中。如果在同一个集合中,则返回 False
    • 如果所有不等式都满足,则返回 True

总结

  • 并查集的作用:维护变量之间的等价关系,支持高效的查找和合并操作。
  • 路径压缩和按秩合并:优化并查集的性能,使得查找和合并操作的时间复杂度接近 O(1)。
  • 测试代码的作用:通过打印每一步的运行结果,验证并查集的正确性和性能。

相关文章:

【并查集】

并查集&#xff08;Disjoint Set Union&#xff0c;DSU&#xff09;是一种用于处理不相交集合的数据结构&#xff0c;主要支持两种操作&#xff1a;查找&#xff08;Find&#xff09;和合并&#xff08;Union&#xff09;。它在解决连通性问题、图论问题以及动态连通性等问题时…...

SQL NOW() 函数详解

SQL NOW() 函数详解 引言 在SQL数据库中&#xff0c;NOW() 函数是一个常用的日期和时间函数&#xff0c;用于获取当前的时间戳。本文将详细介绍 NOW() 函数的用法、参数、返回值以及在实际应用中的注意事项。 函数概述 NOW() 函数返回当前的日期和时间&#xff0c;格式为 Y…...

[EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率

Paper Card 论文标题&#xff1a;FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者&#xff1a;Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...

Rust 条件语句

Rust 条件语句 在编程语言中&#xff0c;条件语句是进行决策和实现分支逻辑的关键。Rust 语言作为一门系统编程语言&#xff0c;其条件语句的使用同样至关重要。本文将详细介绍 Rust 中的条件语句&#xff0c;包括其基本用法、常见场景以及如何避免常见错误。 基本用法 Rust…...

Windows 上安装 PostgreSQL

Windows 上安装 PostgreSQL PostgreSQL 是一款功能强大的开源对象-关系型数据库系统,它具有出色的扩展性和稳定性。本文将详细介绍在 Windows 操作系统上安装 PostgreSQL 的步骤和注意事项。 1. 准备工作 在开始安装 PostgreSQL 之前,请确保您的计算机满足以下要求: 操作…...

UE 5.3 C++ 对垃圾回收的初步认识

一.UObject的创建 UObject 不支持构造参数。 所有的C UObject都会在引擎启动的时候初始化&#xff0c;然后引擎会调用其默认构造器。如果没有默认的构造器&#xff0c;那么 UObject 将不会编译。 有修改父类参数的需求&#xff0c;就使用指定带参构造 // Sets default value…...

解码,蓝桥杯2020G

a2b 解码后&#xff1a;aab ​ #include<iostream> using namespace std; typedef struct Node {char data;int size;Node* next; }Node,*Linklist; char* scan(char str[],int size) {int i 0;Linklist head new Node;Linklist rear head;while (i<size-1) {Lin…...

【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(一)

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;贪心算法篇–CSDN博客 文章目录 一.贪心算法1.什么是贪心算法2.贪心算法的特点 二.例题1.柠…...

Python3 + Qt5:实现AJAX异步更新UI

使用 Python 和 Qt5 开发时异步加载数据的方法 在开发使用 Python 和 Qt5 的应用程序时&#xff0c;为了避免在加载数据时界面卡顿&#xff0c;可以采用异步加载的方式。以下是几种实现异步加载的方法&#xff1a; 1. 使用多线程&#xff08;QThread&#xff09; 通过将数据…...

Windows系统中Docker可视化工具对比分析,Docker Desktop,Portainer,Rancher

Docker可视化工具对比分析&#xff0c;Docker Desktop&#xff0c;Portainer&#xff0c;Rancher Windows系统中Docker可视化工具对比分析1. 工具概览2. Docker Desktop官网链接&#xff1a;主要优点&#xff1a;主要缺点&#xff1a;版本更新频率&#xff1a; 3. Portainer官网…...

从ai产品推荐到利用cursor快速掌握一个开源项目再到langchain手搓一个Text2Sql agent

目录 0. 经验分享&#xff1a;产品推荐 1. 经验分享&#xff1a;提示词优化 2. 经验分享&#xff1a;使用cursor 阅读一篇文章 3. 经验分享&#xff1a;使用cursor 阅读一个完全陌生的开源项目 4. 经验分享&#xff1a;手搓一个text2sql agent &#xff08;使用langchain l…...

curope python安装

目录 curope安装 测试: 报错:libc10.so: cannot open shared object file: No such file or directory 解决方法: curope安装 git clone : GitHub - Junyi42/croco at bd6f4e07d5c4f13ae5388efc052dadf142aff754 cd models/curope/ python setup.py build_ext --inplac…...

低代码产品插件功能一览

下图是统计的目前市面上流行的低代码、零代码产品的插件功能。 产品名称 产品类型 官方插件数量 支持拓展 官方插件功能 宜搭 零代码 3 暂不支持 云打印、CAD看图、打印表单详情 微搭 低代码 1 暂不支持 小程序 明道云 低代码 2 支持 视图、工作流节点 简道…...

流浪 Linux: 外置 USB SSD 安装 ArchLinux

注: ArchLinux 系统为滚动更新, 变化很快, 所以本文中的安装方法可能很快就过时了, 仅供参考. 实际安装时建议去阅读官方文档. 最近, 突然 (也没有那么突然) 有了一大堆 PC: 4 个笔记本, 2 个台式主机 (M-ATX 主板), 1 个小主机 (迷你主机). 嗯, 多到用不过来. 但是, 窝又不能…...

开启 AI 学习之旅:从入门到精通

最近 AI 真的超火,不管是工作还是生活里,到处都能看到它的身影。好多小伙伴都跑来问我,到底该怎么学 AI 呢?今天我就把自己学习 AI 的经验和心得分享出来,希望能帮到想踏入 AI 领域的朋友们! 一、学习内容有哪些 (一)编程语言 Python 绝对是首选!它在 AI 领域的生态…...

笔记:使用ST-LINK烧录STM32程序怎么样最方便?

一般板子在插件上&#xff0c; 8脚 3.3V;9脚 CLK;10脚 DIO;4脚GND ST_Link 19脚 3.3V;9脚 CLK;7脚 DIO;20脚 GND 烧录软件&#xff1a;ST-LINK Utility&#xff0c;Keil_5; ST_Link 接口针脚定义&#xff1a; 按定义连接ST_Link与电路板&#xff1b; 打开STM32 ST-LINK Uti…...

开发环境搭建-4:WSL 配置 docker 运行环境

在 WSL 环境中构建&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 基本概念说明 容器技术 利用 Linux 系统的 文件系统&#xff08;UnionFS&#xff09;、命名空间&#xff08;namespace&#xff09;、权限管理&#xff08;cgroup&#xff09;&#xff0c;虚拟出一…...

925.长按键入

目录 一、题目二、思路三、解法四、收获 一、题目 你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&am…...

【数据采集】案例01:基于Scrapy采集豆瓣电影Top250的详细数据

基于Scrapy采集豆瓣电影Top250的详细数据 Scrapy 官方文档:https://docs.scrapy.org/en/latest/豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:基于Scrapy框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat Python…...

doris:主键模型的导入更新

这篇文档主要介绍 Doris 主键模型基于导入的更新。 整行更新​ 使用 Doris 支持的 Stream Load、Broker Load、Routine Load、Insert Into 等导入方式&#xff0c;向主键模型&#xff08;Unique 模型&#xff09;导入数据时&#xff0c;如果没有相应主键的数据行&#xff0c;…...

日志2025.2.1

日志2025.2.1 1.做了敌人状态机 public class EnermyStateMachine { public EnermyState currentState { get; private set; } public void InitializeState(EnermyState startState) { currentState startState; currentState.Enter(); } public void Change…...

RK3568使用QT操作LED灯

文章目录 一、QT中操作硬件设备思路Linux 中的设备文件操作硬件设备的思路1. 打开设备文件2. 写入数据到设备3. 从设备读取数据4. 设备控制5. 异常处理在 Qt 中操作设备的典型步骤实际应用中的例子:控制 LED总结二、QT实战操作LED灯设备1. `mainwindow.h` 头文件2. `mainwindo…...

Rank-analysis-1.2——一款基于LCU API的排位分析工具,大四学生独立开发

LOL Rank Record Analysis&#xff1a;一款基于LCU API的排位分析工具&#xff0c;大四学生独立开发&#xff01; 大家好&#xff01;我是河南科技学院的大四学生&#xff0c;今天给大家分享一个我自己开发的软件——LOL Rank Record Analysis。这是一个基于 Riot 提供的 LCU …...

关于系统重构实践的一些思考与总结

文章目录 一、前言二、系统重构的范式1.明确目标和背景2.兼容屏蔽对上层的影响3.设计灰度迁移方案3.1 灰度策略3.2 灰度过程设计3.2.1 case1 业务逻辑变更3.2.2 case2 底层数据变更&#xff08;数据平滑迁移&#xff09;3.2.3 case3 在途新旧流程兼容3.2.4 case4 接口变更3.2.5…...

代码随想录-训练营-day17

235. 二叉搜索树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/class S…...

C++,STL,【目录篇】

文章目录 一、简介二、内容提纲第一部分&#xff1a;STL 概述第二部分&#xff1a;STL 容器第三部分&#xff1a;STL 迭代器第四部分&#xff1a;STL 算法第五部分&#xff1a;STL 函数对象第六部分&#xff1a;STL 高级主题第七部分&#xff1a;STL 实战应用 三、写作风格四、…...

《数据可视化新高度:Graphy的AI协作变革》

在数据洪流奔涌的时代&#xff0c;企业面临的挑战不再仅仅是数据的收集&#xff0c;更在于如何高效地将数据转化为洞察&#xff0c;助力决策。Graphy作为一款前沿的数据可视化工具&#xff0c;凭借AI赋能的团队协作功能&#xff0c;为企业打开了数据协作新局面&#xff0c;重新…...

abc 390 D(暴搜 复杂度用 bell数 证明 n 的集合的划分方法的数目)

D题意&#xff1a; 将 长度为 N 的数组 划分为集合 有多少种不同的 异或和 这道题做出来和bell 数没什么关系&#xff0c;如果要证明复杂度那么就需要bell 数 #include <bits/stdc.h> using namespace std; typedef pair<int, int> PII; #define int long long i…...

AJAX综合案例——图书管理

黑马程序员视频地址&#xff1a; AJAX-Day02-10.案例_图书管理AJAX-Day02-10.案例_图书管理_总结_V1.0是黑马程序员前端AJAX入门到实战全套教程&#xff0c;包含学前端框架必会的&#xff08;ajaxnode.jswebpackgit&#xff09;&#xff0c;一套全覆盖的第25集视频&#xff0c…...

计算机网络 笔记 网络层 3

IPv6 IPv6 是互联网协议第 6 版&#xff08;Internet Protocol Version 6&#xff09;的缩写&#xff0c;它是下一代互联网协议&#xff0c;旨在解决 IPv4 面临的一些问题&#xff0c;以下是关于 IPv6 的详细介绍&#xff1a; 产生背景&#xff1a; 随着互联网的迅速发展&…...

Web服务器启动难题:Spring Boot框架下的异常处理解析

摘要 在尝试启动Web服务器时遇到了无法启动的问题&#xff0c;具体错误为org.springframework.boot.web.server.WebServerException。这一异常表明Spring Boot框架在初始化Web服务器过程中出现了故障。通常此类问题源于配置文件错误、端口冲突或依赖项缺失等。排查时应首先检查…...

在LINUX上安装英伟达CUDA Toolkit

下载安装包 wget https://developer.download.nvidia.com/compute/cuda/12.8.0/local_installers/cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm 安装RPM包 sudo rpm -i cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm sudo dnf clean all sudo dnf…...

计算机视觉和图像处理

计算机视觉与图像处理的最新进展 随着人工智能技术的飞速发展&#xff0c;计算机视觉和图像处理作为其中的重要分支&#xff0c;正逐步成为推动科技进步和产业升级的关键力量。 一、计算机视觉的最新进展 计算机视觉&#xff0c;作为人工智能的重要分支&#xff0c;主要研究如…...

Spring Boot 日志:项目的“行车记录仪”

一、什么是Spring Boot日志 &#xff08;一&#xff09;日志引入 在正式介绍日志之前&#xff0c;我们先来看看上篇文章中&#xff08;Spring Boot 配置文件&#xff09;中的验证码功能的一个代码片段&#xff1a; 这是一段校验用户输入的验证码是否正确的后端代码&#xff0c…...

ComfyUI中For Loop的使用

研究了半天&#xff0c;终于弄明白了如何使用For Loop。 1、在For中节点&#xff0c;必须有输出连接到For Loop End的initial_value点&#xff0c;才能确保节点执行完毕后才 进入下一轮循环&#xff0c;否则&#xff0c;可能导致节点没执行完&#xff0c;就进入下一个循环了。…...

网站快速收录:利用网站导航优化用户体验

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/44.html 网站快速收录与用户体验的提升密切相关&#xff0c;而网站导航作为用户访问网站的“指南针”&#xff0c;其优化对于实现这一目标至关重要。以下是一些利用网站导航优化用户体验&am…...

FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验

文章目录 FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验概述笔记my_fltk_test.cppfltk_test.hfltk_test.cxx用adjuster工程试了一下&#xff0c;好使。END FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验 概述 用fluid搭建UI…...

c#aot做跨平台动态库

c#aot技术&#xff0c;很多人都觉得是垃圾&#xff0c;没有用&#xff0c;其实还是很有用的。.net发展这么多年&#xff0c;有很多很好的功能&#xff0c;你可以把这些功能做成动态库供rust调用&#xff0c;供c/c调用。你还真别看不起这些功能&#xff0c;当你需要&#xff0c;…...

使用Pygame制作“打砖块”游戏

1. 前言 打砖块&#xff08;Breakout / Arkanoid&#xff09; 是一款经典街机游戏&#xff0c;玩家控制一个可左右移动的挡板&#xff0c;接住并反弹球&#xff0c;击碎屏幕上方的砖块。随着砖块被击碎&#xff0c;不仅能获得分数&#xff0c;还可以体验到不断加速或复杂的反弹…...

Autosar-以太网是怎么运行的?(原理部分)

写在前面&#xff1a; 入行一段时间了&#xff0c;基于个人理解整理一些东西&#xff0c;如有错误&#xff0c;欢迎各位大佬评论区指正&#xff01;&#xff01;&#xff01; 1.TCP/IP协议详解 TCP/IP协议包含了一系列的协议&#xff0c;也叫TCP/IP协议族&#xff08;TCP/IP P…...

小程序-基础加强-自定义组件

前言 这次讲自定义组件 1. 准备今天要用到的项目 2. 初步创建并使用自定义组件 这样就成功在home中引入了test组件 在json中引用了这个组件才能用这个组件 现在我们来实现全局引用组件 在app.json这样使用就可以了 3. 自定义组件的样式 发现页面里面的文本和组件里面的文…...

线程池以及在QT中的接口使用

文章目录 前言线程池架构组成**一、任务队列&#xff08;Task Queue&#xff09;****二、工作线程组&#xff08;Worker Threads&#xff09;****三、管理者线程&#xff08;Manager Thread&#xff09;** 系统协作流程图解 一、QRunnable二、QThreadPool三、线程池的应用场景W…...

Cubemx文件系统挂载多设备

cubumx版本&#xff1a;6.13.0 芯片&#xff1a;STM32F407VET6 在上一篇文章中介绍了Cubemx的FATFS和SD卡的配置&#xff0c;由于SD卡使用的是SDIO通讯&#xff0c;因此具体驱动不需要自己实现&#xff0c;Cubemx中就可以直接配置然后生成SDIO的驱动&#xff0c;并将SD卡驱动和…...

NeetCode刷题第19天(2025.1.31)

文章目录 099 Maximum Product Subarray 最大乘积子数组100 Word Break 断字101 Longest Increasing Subsequence 最长递增的子序列102 Maximum Product Subarray 最大乘积子数组103 Partition Equal Subset Sum 分区等于子集和104 Unique Paths 唯一路径105 Longest Common Su…...

网站快速收录:如何设置robots.txt文件?

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/34.html 为了网站快速收录而合理设置robots.txt文件&#xff0c;需要遵循一定的规则和最佳实践。robots.txt文件是一个纯文本文件&#xff0c;它告诉搜索引擎爬虫哪些页面可以访问&#xff…...

(超实用教程)利用MSF制作exe程序木马

msfvenom 是攻击载荷&#xff08;payload&#xff09;生成器 格式&#xff1a; msfvenom -p windows/meterpreter/reverse_tcp LHOST192.168.110.32 LPORT4456 -f exe -o payload1.exe -p&#xff1a;指定需要使用的payload -f&#xff1a;指定输出格式 -o&#xff1a;保…...

Spring Boot + Facade Pattern : 通过统一接口简化多模块业务

文章目录 Pre概述在编程中&#xff0c;外观模式是如何工作的&#xff1f;外观设计模式 UML 类图外观类和子系统的关系优点案例外观模式在复杂业务中的应用实战运用1. 项目搭建与基础配置2. 构建子系统组件航班服务酒店服务旅游套餐服务 3. 创建外观类4. 在 Controller 中使用外…...

4 [危机13小时追踪一场GitHub投毒事件]

事件概要 自北京时间 2024.12.4 晚间6点起&#xff0c; GitHub 上不断出现“幽灵仓库”&#xff0c;仓库中没有任何代码&#xff0c;只有诱导性的病毒文件。当天&#xff0c;他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒&#xff0c;等待不…...

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…...

10 Flink CDC

10 Flink CDC 1. CDC是什么2. CDC 的种类3. 传统CDC与Flink CDC对比4. Flink-CDC 案例5. Flink SQL 方式的案例 1. CDC是什么 CDC 是 Change Data Capture&#xff08;变更数据获取&#xff09;的简称。核心思想是&#xff0c;监测并捕获数据库的变动&#xff08;包括数据或数…...