2022-11-22 23:08阅读: 221评论: 0推荐: 0

并查集算法

并查集(Union-Find)算法

简介

Union Find算法用于处理集合的合并查询问题,它定义了两个用于并查集的操作:

  • find :确定元素属于哪一个子集,或判断两个元素是否属于同一子集;
  • union :将两个子集合并为一个子集。

思路

使用一维数组来记录每一个节点的父节点,如果它是根节点,就指向自己。

我们使用数组 parent[n] 来记录节点的指向关系,即 parent[i] 的值就表示节点 i 的父节点。

特别地,如果节点 i 等于 parent[i] ,那么节点 i 就是该连通分量的根节点。

代码实现

这里,我们直接将并查集算法总结为一个通用的工具类,代码实现如下:

  • Java 实现

注:java 的代码实现中,查找时的压缩效率更高,所以就不需要 size 去平衡了。

public class UnionFound {
private int count;
private final int [] parent;
public UnionFound(int n) {
parent = new int[n];
count = n;
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public boolean union(int a, int b) {
int root1 = find(a);
int root2 = find(b);
if (root1 == root2) {
return false;
}
parent[root1] = root2;
count--;
return true;
}
public boolean isConnected(int a, int b) {
return find(a) == find(b);
}
public int getCount() {
return count;
}
}
  • Python 实现
class UnionFind(object):
def __int__(self, n: int):
self._count = n
self._parent = [i for i in range(n)]
# 记录每棵树的重量
self._size = [1] * n
def union(self, p: int, q: int):
""" 连接两个节点 """
root_p = self.find(p)
root_q = self.find(q)
if root_p == root_q:
return False
# 将小树接到大树下面:保证树平衡
if self._size[root_p] > self._size[root_q]:
self._parent[root_q] = root_p
self._size[root_p] += self._size[root_q]
else:
self._parent[root_p] = root_q
self._size[root_q] += self._size[root_p]
self._count -= 1
return True
def connected(self, p: int, q: int):
return self.find(p) == self.find(q)
def find(self, x: int):
""" 返回某个节点的根节点 """
while self._parent[x] != x:
# 压缩路径:使当前节点的父节点指向父节点的父节点
self._parent[x] = self._parent[self._parent[x]]
x = self._parent[x]
return x
def get_count(self):
return self._count

应用

利用上面总结的并查集工具类,可以直接解决多道力扣上的并查集算法题。

应用1:Leetcode.130

题目

130. 被围绕的区域

分析

这道题最简单的解法是使用 DFS 或者 BFS 算法,这里为了介绍并查集算法,所以,我们使用并查集求解。

为了使用并查集算法,我们需要将 nm 的二维数组转换为以为一维数组,以二维坐标 (x, y) 为例:

(x, y)xn+y

注意,这是将二维坐标映射到一维坐标常用的技巧。

首先,将边界四周为 "O" 的元素的父节点指向一个虚拟节点 dummy ,由于初始化并查集数组最多会用到 mn1 , 所以,这里我们假设:

dummy=nm

然后,再遍历内部元素,如果当前元素为 "O",则将该元素与其四周为 "O" 的元素连通。

最后,再遍历一次所有元素,将没有与 dummy 节点连通元素置为 "X" 即可。

代码实现

from typing import List
DIRECTIONS = [(1, 0), (-1, 0), (0, 1), (0, -1)]
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
if len(board) == 0:
return
m, n = len(board), len(board[0])
uf = UnionFind(m * n + 1)
dummy = m * n
# 遍历第一列和最后一列元素,将为O的元素与dummy相连
for i in range(m):
if board[i][0] == "O":
uf.union(i * n, dummy)
if board[i][n - 1] == "O":
uf.union(i * n + n - 1, dummy)
# 遍历第一行和最后一行元素,将为O的元素与dummy相连
for j in range(n):
if board[0][j] == "O":
uf.union(j, dummy)
if board[m - 1][j] == "O":
uf.union((m - 1) * n + j, dummy)
# 将与边界相连的"O"连通
for i in range(1, m - 1):
for j in range(1, n - 1):
# 如果当前元素是O
if board[i][j] == "O":
# 将它四周是O的元素连通
for direction in DIRECTIONS:
x, y = direction[0] + i, direction[1] + j
if board[x][y] == "O":
uf.union(x * n + y, i * n + j)
for i in range(m):
for j in range(n):
# 如果它和dummy不是连通的,就置为X
if not uf.connected(i * n + j, dummy):
board[i][j] = "X"
return board

应用2:Leetcode.323

题目

323. 无向图中连通分量的数目

分析

使用并查集算法,依次遍历所有的边,将所有的边连接即可,最后返回连通分量即可。

代码实现

from typing import List
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
uf = UnionFind(n)
for edge in edges:
uf.union(edge[0], edge[1])
return uf.get_count()

应用3:Leetcode.261

题目

261.以图判树

分析

使用并查集算法,判断图是否能成为一棵树,连通分量必须为1,并且任意两点不能成环。
判断是否成环,可以通过连接两个节点,如果已经是连通的,就有环,如果所有的点都可以连通,则没有成环。

代码实现

from typing import List
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
uf = UnionFind(n)
for edge in edges:
if not uf.union(edge[0], edge[1]):
return False
return uf.get_count() == 1

应用4:Leetcode.684

题目

684. 冗余连接

分析

我们使用并查集,假设初始所有的节点都是属于不同的连通分量,遍历所有的边:

  • 如果当前两个节点不相连,则连通两个节点;
  • 如果当前两个节点已经连通相连,则说明这两个节点遍历在前面的边时,就已经连通,再连接就会成环,所以,这两个点就是答案。

注意:题目中的节点是从 1 开始的,我们的工具类 UnionFind 假设节点序号是从 0 作为起始的,为了避免转换所有的节点,我们直接将连通分量数设置为 n+1

代码实现

from typing import List
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
uf = UnionFind(n + 1)
for edge in edges:
if not uf.connected(edge[0], edge[1]):
uf.union(edge[0], edge[1])
else:
return edge
return []

应用5:Leetcode.684

题目

990. 等式方程的可满足性

分析

由于表达式的变量只有单个的小写字母,因此,我们可以维护一个长度为 26 的并查集。

先将表达式中相等的变量用并查集连接,然后,对于不相等的变量,判断它们的联通性即可。

代码实现

class Solution {
public boolean equationsPossible(String[] equations) {
UnionFound uf = new UnionFound(26);
// 先将所有相等的变量联通
for (String equation: equations) {
int a = equation.charAt(0) - 'a';
int b = equation.charAt(3) - 'a';
if (equation.charAt(1) == '=') {
uf.union(a, b);
}
}
// 判断不相等的变量是否是联通的
for (String equation: equations) {
int a = equation.charAt(0) - 'a';
int b = equation.charAt(3) - 'a';
if (equation.charAt(1) == '!' && uf.isConnected(a, b)) {
return false;
}
}
return true;
}
}

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/16916856.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(221)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.