并查集概念
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
并查集也被称为不相交集数据结构。顾名思义,并查集主要操作是合并与查询,它是把初始不相交的集合经过多次合并操作后合并为一个大集合,然后可以通过查询判断两个元素是否已经在同一个集合中了。
在Python中,并查集可以通过list或者dict等数据结构实现
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
并查集的主要方法有:查找/连通/判断是否连通
这道题的代码相对较为简单:
class Solution:
def findCircleNum(self, M: List[List[int]]) -> int:
n = len(M)
p = [[i] for i in range(n)] #先生成并查集的节点
ans = n
for i in range(n):
for j in range(i):
if M[i][j] == 1 and p[i] is not p[j]: #如果i和j是好朋友,但p中没有显示则
p[i] += p[j] #在p[i]中加入p[j]
for k in p[j]: #对于p[j]中的每个数字,定义其根节点为p[i]
p[k] = p[i]
ans -= 1 #朋友圈的数量 - 1
print(p) #本题结果为[[1, 0], [1, 0], [2]]
return ans
在这道题目中传统的方式可以将各个方法都实现出来,如下:
class Solution(object):
def findCircleNum(self, M):
"""
:type M: List[List[int]]
:rtype: int
"""
# 声明表示集合的树,用数组表示树,如 parent[5] == -1 表示
# 5 是某个集合的代表,同时也是树根
parent = [-1] * len(M)
# 为各个集合的深度排个序
rank = dict()
# 只遍历左下部分
for i in range(len(M)):
for j in range(i):
if M[i][j] == 1:
# 如果两人是朋友,就把两人放入同一个集合
self.union(i, j, parent, rank)
ans = 0
print(parent)
for i in parent: # 查看有多少集合,即多少个朋友圈
if i == -1:
ans += 1
return ans
def findRoot(self, num, parent):
"""
:num:某个小朋友
:parent:多个集合树
一直找到这个小朋友所在集合的代表,即树根
"""
while parent[num] != -1:
num = parent[num]
return num
def union(self, x, y, parent, rank):
"""
x,y 表示满足朋友关系的两小朋友
"""
if x == y: # 表示自己和自己是朋友,这个直接返回
return
x = self.findRoot(x, parent) # 找到所在集合的代表,可能就是自己
print("x is",x)
y = self.findRoot(y, parent)
print("y is",y)
rank_x = rank[x] if x in rank else 0 # 查询该集合目前的深度
rank_y = rank[y] if y in rank else 0
if x == y: # 检测到 x, y 已经是在同一个集合了,直接返回
return
if rank_x > rank_y: # 这里是做路径压缩的,避免树太深使得 findRoot 函数耗时过多
parent[y] = x
elif rank_x < rank_y:
parent[x] = y
else:
parent[x] = y
rank[y] = 1