并查集:一种用于支持集合快速合并和查找操作的数据结构
- O(1) 合并两个集合- Union
- O(1) 查询元素所属集合- Find
Union Find 是一棵多叉树:
1、并查集的实现
1.1 底层数据结构
父亲表示法,用一个数组/哈希表记录每个节点的父亲是谁。
- father[“Tom”] = “Nlear”
- father[“Jim”] = “Soox”
1.2 查询所在集合
- 用所在集合最顶层的老大哥节点来代表这个集合
1.3 合并两个集合
- 找到两个集合中最顶层的两个老大哥节点A 和B
- father[A] = B // or father[B] = A 如果无所谓谁合并谁的话
2、代码模板
2.1 初始化
使用哈希表或者数组来存储每个节点的父亲节点
def init(nums):
n = len(nums)
father = {}
for i in range(n):
father[i] = i
2.2 查找和压缩
- 沿着父亲节点一路往上走就能找到根节点
- 在找到根节点以后,还需要把一路上经过的点都指向根节点
def find(target, father):
path = []
while target != father[target]:
path.append(target)
target = father[target]
for p in path:
father[p] = target
return target
2.3 合并
- 找到两个元素所在集合的两个根节点a 和b
- 将其中一个根节点的父指针指向另外一个根节点
def union(point_a, point_b, father):
root_a = find(point_a)
root_b = find(point_b)
if root_a != root_b:
father[root_a] = root_b
3、常见问题:
- 判断两个元素是否在同一个集合
- 获得某个集合的元素个数
- 统计当前集合个数
总结:
- 跟连通性有关的问题,都可以使用BFS 和Union Find
- 需要拆开两个集合的时候无法使用Union Find
3.1、连接图
给一个图中的n
个节点, 记为 1
到 n
. 在开始的时候图中没有边。
你需要完成下面两个方法:
connect(a, b)
, 添加连接节点a
,b
的边.query(a, b)
, 检验两个节点是否联通
来自:九章算法
链接:https://www.jiuzhang.com/solution/connecting-graph/#tag-highlight-lang-python
解析:简单并查集实现并和查操作。
class ConnectingGraph:
def __init__(self, n):
self.father = {}
for i in range(1, n + 1):
self.father[i] = i
def connect(self, a, b):
self.father[self.find(a)] = self.find(b)
def query(self, a, b):
return self.find(a) == self.find(b)
def find(self, node):
path = []
while self.father[node] != node:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
3.2、连接图 II
给一个图中的 n
个节点, 记为 1
到 n
.在开始的时候图中没有边.
你需要完成下面两个方法:
connect(a, b)
, 添加一条连接节点 a, b的边query(a)
, 返回图中含a
的联通区域内节点个数
来自:九章算法
链接:https://www.jiuzhang.com/solution/connecting-graph-ii/#tag-highlight-lang-python
解析:在根节点上记录这个集合的元素个数并查集。同时维护一下个数即可。
class ConnectingGraph2:
def __init__(self, n):
self.father = {}
self.count = {}
for i in range(1, n + 1):
self.father[i] = i
self.count[i] = 1
def connect(self, a, b):
root_a = self.find(a)
root_b = self.find(b)
if root_a != root_b:
self.father[root_a] = root_b
self.count[root_b] += self.count[root_a]
def query(self, a):
return self.count[self.find(a)]
def find(self, node):
path = []
while node != self.father[node]:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
3.3、连接图 III
给一个图中的 n
个节点, 记为 1
到 n
. 在开始的时候图中没有边.
你需要完成下面两个方法:
connect(a, b)
, 添加一条连接节点 a, b的边query()
, 返回图中联通区域个数
来自:九章算法
链接:https://www.jiuzhang.com/solution/connecting-graph-iii/#tag-highlight-lang-python
分析:实时维护区域的个数,即若在某一次合并中两个区域合并成一个,那么数量-1。
class ConnectingGraph3:
def __init__(self, n):
self.size = n
self.father = {}
for i in range(1, n + 1):
self.father[i] = i
def connect(self, a, b):
root_a = self.find(a)
root_b = self.find(b)
if root_a != root_b:
self.father[root_a] = root_b
self.size -= 1
def query(self):
return self.size
def find(self, node):
path = []
while node != self.father[node]:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
3.4、 账户合并
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/accounts-merge