题目描述:
题解:
可以看到,如果节点是相互连通的(从一个节点可以到达另一个节点),那么他们在同一棵树里,或者说在同一个集合里,或者说他们的祖先是相同的。
(1)初始化
当把一个新节点添加到并查集中,它的父节点应该为空,同时集合数量+1
(2)合并两个节点
如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。谁做父节点一般是没有区别的。同时集合数量-1
(3)查找祖先和路径压缩
如果节点的父节点不为空,那就不断迭代
这里有一个优化的点:如果我们树很深,比如说退化成链表,那么每次查询的效率都会非常低。所以我们要做一下路径压缩。也就是把树的深度固定为二。
这么做可行的原因是,并查集只是记录了节点之间的连通关系,而节点相互连通只需要有一个相同的祖先就可以了。
class UnionFind:
def __init__(self):
# 记录父结点
self.father = {}
# 记录集合的数量
self.num_of_sets = 0
def find(self,x):
root = x
#寻找父结点
while self.father[root] != None:
root = self.father[root]
#路径压缩
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
#聚合两个结点
def merge(self,x,y):
root_x,root_y = self.find(x),self.find(y)
#如果父结点不同,则合并
if root_x != root_y:
self.father[root_x] = root_y
# 集合的数量-1
self.num_of_sets -= 1
def add(self,x):
if x not in self.father:
self.father[x] = None
# 集合的数量+1
self.num_of_sets += 1
class Solution:
def findCircleNum(self, M: List[List[int]]) -> int:
uf = UnionFind()
for i in range(len(M)):
uf.add(i)
for j in range(i):
if M[i][j]:
uf.merge(i,j)
return uf.num_of_sets