在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x)Find(x) 返回 xx 所属集合的代表,而 Union 使用两个集合的代表作为参数。
f = {}
#print(name_age)
def find(x):
f.setdefault(x, x)
if f[x] != x:
f[x] = find(f[x])
return f[x]
def union(x, y):
f[find(x)] = find(y)
下面来介绍几个使用并查集的典型例题
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
此题属于典型的求连通域的题目,一种比较简单的思路是 从边界出发吧,先把边界上和 O 连通点找到, 把这些变成 B,然后遍历整个 board 把 O 变成 X, 把 B 变成 O。可以很简单得用DFS或者BFS实现,这就不放代码了,也可以用并查集 把边界 O 并且与它连通这些点分在一起。
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
f = {}
def find(x):
f.setdefault(x, x)
if f[x] != x:
f[x] = find(f[x])
return f[x]
def union(x, y):
f[find(y)] = find(x)
if not board or not board[0]:
return
row = len(board)
col = len(board[0])
dummy = row * col
for i in range(row):
for j in range(col):
if board[i][j] == "O":
#将所有边界的0y与自定义的dummy连接在一起 使dummy作为“首领”
if i == 0 or i == row - 1 or j == 0 or j == col - 1:
union(i * col + j, dummy)
#print(i * col + j, dummy)
else:
#将所有不与边界相临的连接在一起
for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if board[i + x][j + y] == "O":
union(i * col + j, (i + x) * col + (j + y))
#print(f)
tmp = find(dummy)
for i in range(row):
for j in range(col):
#print(find(i * col + j))
# 对每一个点 在并查集里找它的首领 没有被遍历到的X的首领是自己
# 如果遍历出来首领是dummy 则不变 否则变为X
if tmp == find(i * col + j):
board[i][j] = "O"
else:
board[i][j] = "X"
给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。
你可以 任意多次交换 在 pairs 中任意一对索引处的字符。
返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。
示例 1:
输入:s = “dcab”, pairs = [[0,3],[1,2]]
输出:“bacd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[1] 和 s[2], s = “bacd”
思路:由于我们有pairs数组 可以很方便的求出那些元素是在一起的 可以任意调换位置的
class Solution:
def smallestStringWithSwaps(self, s: str, pairs: [int]) -> str:
f = {} #初始化并查集
def find(x):
f.setdefault(x, x)
if x != f[x]:
f[x] = find(f[x])
return f[x]
def union(i,j):
f[find(i)] = find(j)
# 将可以交换的元素位置联通
for i, j in pairs:
union(i,j)
#合并可交换位置
d = collections.defaultdict(list)
# 对每一个元素 将它加入它的首领的字典里面
for i, j in f.items():
#print(i,j)
d[find(i)].append(i)
#排序
ans = list(s)
for q in d.values():
# 将每个集合分别排序 找到这个集合里面字典序最小的
t = sorted(ans[i] for i in q)
#print(t)
# 按照原有的位置插入
for i, c in zip(sorted(q), t):
ans[i] = c
return ''.join(ans)
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
示例 1:
输入:n = 4, connections = [[0,1],[0,2],[1,2]]
输出:1
解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。
思路:首先可以看出 如果connections的数目小于n-1 那么肯定是不行的 之后可以找到有多少个是已经连接了的m以及有多少个首领p,那么最后需要操作的次数为n-m+p-1 也可以对0—n求所有的首领 最后不同首领的个数-1
class Solution:
def makeConnected(self, n: int, connections: List[List[int]]) -> int:
f = {}
if len(connections) < n - 1:
return -1
def find(x):
f.setdefault(x, x)
if f[x] != x:
f[x] = find(f[x])
return f[x]
def union(x, y):
f[find(x)] = find(y)
for a, b in connections:
union(a,b)
#print(f,len(f))
res = []
for j in f:
res.append(find(j))
#print(len({*map(find, f)}))
return n-len(f)+len({*map(find, f)})-1
每年,政府都会公布一万个最常见的婴儿名字和它们出现的频率,也就是同名婴儿的数量。有些名字有多种拼法,例如,John 和 Jon 本质上是相同的名字,但被当成了两个名字公布出来。给定两个列表,一个是名字及对应的频率,另一个是本质相同的名字对。设计一个算法打印出每个真实名字的实际频率。注意,如果 John 和 Jon 是相同的,并且 Jon 和 Johnny 相同,则 John 与 Johnny 也相同,即它们有传递和对称性。
在结果列表中,选择字典序最小的名字作为真实名字。
示例:
输入:names = [“John(15)”,“Jon(12)”,“Chris(13)”,“Kris(4)”,“Christopher(19)”], synonyms = ["(Jon,John)","(John,Johnny)","(Chris,Kris)","(Chris,Christopher)"]
输出:[“John(27)”,“Chris(36)”]
看起来过程很繁琐 其实只要理清思路
1.首先分割synonyms数组字符串找出所有人名 将他们联通起来 注意如果有人名没有在names出现过 可以不统计
2 分割namse字符串 用一个字典统计所有人名以及他们的次数
3 对names里面的每一个名字 将他们加入他们首领的集合
4.对每一个集合 先进行排序 保证字典序最小的名字在第一个 之后统计他们在字典中的次数和
5 组合起来加入答案数组
class Solution:
def trulyMostPopular(self, names: List[str], synonyms: List[str]) -> List[str]:
name_age = collections.defaultdict()
for str1 in names:
temp = str1.split('(')
name_age[temp[0]]=int(temp[1][0:-1])
f = {}
#print(name_age)
def find(x):
f.setdefault(x, x)
if f[x] != x:
f[x] = find(f[x])
return f[x]
def union(x, y):
f[find(x)] = find(y)
for str2 in synonyms:
temp2 = str2.split(',')
name1 = temp2[0][1:]
name2 = temp2[1][0:-1]
if name1 not in name_age or name2 not in name_age:
continue
union(name1,name2)
same_name = collections.defaultdict(list)
for j in name_age:
same_name[find(j)].append(j)
ans = []
for name_list in same_name.values():
name_list.sort()
sum1 = 0
for j in name_list:
sum1 +=name_age[j]
ans.append(name_list[0]+'('+str(sum1)+')')
return ans
并查集的题目大多都有一个相似的前提 就是有一个临结矩阵 或者数组 可以初始化并查集
此外比较有意思的题目还有947. 移除最多的同行或同列石头 由于并查集是1维的 在处理二维坐标数据时往往对其进行编号 如第一题 或者将列乘以一个很大的数。