1 并查集实际上可以看做是一个有向图的树,除了根节点指向自己,其它的节点都是向上指,指向其父节点,
class UnionFindSets: def __init__(self, M): self.tree_num = len(M) # 初始化树的节点数 self.tree_node_num = [1] * self.tree_num # 索引是节点,数组中的值是其父节点 self.parent = [i for i in range(self.tree_num)] def union(self, node_i, node_j): root_i = self.find(node_i) root_j = self.find(node_j) # 如果是同一个根节点,则返回,无需合并,否则合并 if root_i == root_j: return # 合并的时候小树往大树上合并,这样更加均衡,因为大树一般较高,往小树合并的话高度又会加一,导致越合越高, # 并且计算树的节点个数,方便合并时比较树的大小, if self.tree_node_num[root_i] > self.tree_node_num[root_j]: self.parent[root_j] = root_i self.tree_node_num[root_i] += self.tree_node_num[root_j] else: self.parent[root_i] = root_j self.tree_node_num[root_j] += self.tree_node_num[root_i] # 每合并一次减少一个树,树的数量减一 self.tree_num -= 1 # 查找节点所在树的根 def find(self, node): # 如果还没有到根节点,就继续往上找 while node != self.parent[node]: # 为了压缩树的高度,将当前节点连到其爷爷节点上,这样树的高度不超过3 self.parent[node] = self.parent[self.parent[node]] # 更新当前节点,即向上走一个到其父节点 node = self.parent[node] # 返回树的根节点 return node from typing import List class Solution: def findCircleNum(self, M: List[List[int]]) -> int: uf = UnionFindSets(M) l = len(M) for row in range(l): for col in range(row): # 如果为1,说明是朋友,进行连接 if M[row][col]: uf.union(row, col) return uf.tree_num
2 并查集的优化有两种策略:
(1)路径压缩;
有“隔代压缩”与“完全压缩”。
“隔代压缩”性能比较高,虽然压缩不完全,不过多次执行“隔代压缩”也能达到“完全压缩”的效果,我本人比较偏向使用“隔代压缩”的写法。
“完全压缩”需要借助系统栈,使用递归的写法。或者先找到当前结点的根结点,然后把沿途上所有的结点都指向根结点,得遍历两次。
(2)按秩合并。
秩也有两种含义:① 秩表示以当前结点为根结点的子树结点总数,即这里的“秩”表示 size 含义;② 秩表示以当前结点为根结点的子树的高度,即这里的“秩”表示 rank 含义