Union-Find 并查集算法
首先吐槽下这个翻译. 只看"并查集"完全 get 不到这个算法是干什么的. 没找到这个翻译的出处. 从字面意思来看个人认为翻译成合并查找更合适.
Union-Find 算法,也称为并查集(Disjoint Set),是一种用于解决集合合并和查询连通性的数据结构和算法。 把所有连通的节点合并到一个根节点上. 当需要判断2个节点是否相连时, 只需要判断他们的根节点是否相同即可
连通性
连通性是图论中的一个概念. 指的是在一个图或网络中,节点之间相连的特征, 即2个节点之间有边连接。
在图论中,连通性是图的基本特性之一。一个无向图称为连通图,如果图中的任意两个节点都是连通的,也就是说,对于图中的任意两个节点 u 和 v, 都存在一条从节点 u 到节点 v 的路径。
相反地,如果图不是连通的,则可以将其分解为若干个连通的子图,每个子图中的节点互相连通,但不与其他子图的节点连通。
连通性在计算机科学和网络理论中有广泛应用。例如,在社交网络分析中,连通性可以帮助我们识别社交群体或寻找影响力最大的节点。在网络路由中,连通性是确保数据包能够正确传递到目的地的重要属性。
连通性有以下特点:
- 可传递性:如果节点 A 与节点 B 连通,并且节点 B 与节点 C 连通,则节点 A 与节点 C 也连通。
- 自反性:每个节点与自身是连通的。
- 对称性:如果节点 A 与节点 B 连通,则节点 B 与节点 A 也连通。
- 等价关系:连通性满足等价关系的性质,即自反性、对称性和传递性。
Union-Find 算法
Union-Find 算法在解决一些图论问题、连通性问题和动态等价类问题等方面非常有用。它具有简单、高效且易于实现的特点,常被应用于各种算法和系统设计中。 该算法维护一个森林(或称为集合森林),其中每个元素都有一个指向其父节点的指针。初始状态下,每个元素都是独立的一个集合。
Union-Find 算法提供以下两个主要操作:
- Find(查找):用于找到元素所属的根节点。该操作通过递归地沿着指向父节点的指针向上查找,直到找到根节点。 这可以用来确定两个元素是否属于同一个集合,只需比较它们的根节点是否相同。
- Union(合并):用于将两个集合合并为一个集合。该操作将一个集合的根节点作为另一个集合的根节点的子节点,从而实现两个集合的合并。
算法优化: Union-Find 算法通常使用优化技巧,如路径压缩(Path Compression)和按秩合并(Union by Rank)来提高效率。
- 路径压缩可以减少查找操作的时间复杂度,使得树的高度更加平衡;
- 按秩合并则基于树的"秩"(即树的深度或节点数量)来决定合并时的策略,以减少树的高度增长。
实现方法
class UnionFind:
def __init__(self, n):
self.parent = list(range(n)) # 初始化时每个节点的 root 是自身
self.rank = [0] * n # 每个节点的树的大小, 用于按秩优化.
def find(self, x):
"""
查看节点 x 的根节点
"""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # 路径压缩,将节点直接连接到根节点上
return self.parent[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return
# 把秩小的连接到秩大的根节点下, 不会修改该根节点的秩
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
应用场景举例
判断被围绕的区域. 如下数组表示一个棋盘. 判断其中被 X 围绕的区域
示例:
arr = [
["X","O","X","O","X","O"],
["O","X","O","X","O","X"],
["X","O","X","O","X","O"],
["O","X","O","X","O","X"]
]
其中被X围绕的点O, 即点 [1, 2],[1, 4],[2, 1],[2, 3]
被包围的点即与边缘不连通的. 因此可以用 Union-Find 方法来判断连通性
步骤:
- 让边界上的点连到同一个根节点上
- 把上下相同的点连接
- 找到与边界的根节点不相连的 O
解法如下:
def get_surrounded_pos(board: List[List[str]]) -> List[List[int]]:
"""
填充被包围的 O
"""
row_len = len(board)
col_len = len(board[0])
all_o_list = []
out_root = row_len * col_len
uf = UnionFind(row_len * col_len + 1)
# 把最后一个节点作为根节点, 使边缘的元素都与该根节点相连
for row in range(0, row_len):
for col in range(0, col_len):
char = board[row][col]
char_idx = row * col_len + col
# 边缘节点与 out_root 相连
if col == 0 or col == col_len - 1 or row == 0 or row == row_len - 1:
uf.union(out_root, char_idx)
# 上下左右的点如果相同的话则相连
if row > 0 and board[row - 1][col] == char:
uf.union((row - 1) * col_len + col, char_idx)
if row < row_len - 1 and board[row + 1][col] == char:
uf.union((row + 1) * col_len + col, char_idx)
if col > 0 and board[row][col - 1] == char:
uf.union((col - 1) + row * col_len, char_idx)
if col < col_len - 1 and board[row][col + 1] == char:
uf.union((col + 1) + row * col_len, char_idx)
if char == 'O':
all_o_list.append([row, col])
# 找到不与外界连通的元素
return [item for item in all_o_list if not uf.is_connect(item[0] * col_len + item[1], out_root)]