并查集算法
并查集(Union-Find)算法
简介
Union Find算法用于处理集合的合并和查询问题,它定义了两个用于并查集的操作:
:确定元素属于哪一个子集,或判断两个元素是否属于同一子集; :将两个子集合并为一个子集。
思路
使用一维数组来记录每一个节点的父节点,如果它是根节点,就指向自己。
我们使用数组
特别地,如果节点
代码实现
这里,我们直接将并查集算法总结为一个通用的工具类,代码实现如下:
- 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
题目
分析
这道题最简单的解法是使用
或者 算法,这里为了介绍并查集算法,所以,我们使用并查集求解。
为了使用并查集算法,我们需要将
注意,这是将二维坐标映射到一维坐标常用的技巧。
首先,将边界四周为 "O" 的元素的父节点指向一个虚拟节点
然后,再遍历内部元素,如果当前元素为 "O",则将该元素与其四周为 "O" 的元素连通。
最后,再遍历一次所有元素,将没有与
代码实现
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
题目
分析
使用并查集算法,依次遍历所有的边,将所有的边连接即可,最后返回连通分量即可。
代码实现
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
题目
分析
使用并查集算法,判断图是否能成为一棵树,连通分量必须为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
题目
分析
我们使用并查集,假设初始所有的节点都是属于不同的连通分量,遍历所有的边:
- 如果当前两个节点不相连,则连通两个节点;
- 如果当前两个节点已经连通相连,则说明这两个节点遍历在前面的边时,就已经连通,再连接就会成环,所以,这两个点就是答案。
注意:题目中的节点是从
代码实现
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
题目
分析
由于表达式的变量只有单个的小写字母,因此,我们可以维护一个长度为
先将表达式中相等的变量用并查集连接,然后,对于不相等的变量,判断它们的联通性即可。
代码实现
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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步