并查集:–使用的数据结构:字典
形式也是通过 key-value来记录关系,key都是不同的,value可以是相同的.
如下: res[0] = 1 , res[1] = 2 , res[2] = 2
初始化 :把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
查找:查找元素所在的集合,即根节点。
合并:将两个元素所在的集合合并为一个集合。通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现
下面一些理解是看labuladong的,很赞!
并查集是用来查询两个元素是否属于同一个集合,并且快速合并两个元素所在的集合。最终判断有多少个不连通的集合,这个过程中,朋友的朋友都是朋友,都算是一个集合之中。
通过集合中的一个元素来代表这个集合,转化为树的结构就是树的根节点。所以集合中每个节点都有一个父亲节点,根节点的父亲节点是他自己。
f = {}
f.setfault(key, value)
字典的setdefault(),如果字典中已经有这个key了,那么是无效的。只有没有这个键的情况下生效。
dict.setdefault(key, default=None)
参数
key – 查找的键值。
default – 键不存在时,设置的默认键值。
返回值
如果字典中包含有给定键,则返回字典中该键对应的值,否则返回为该键设置的值。
实例
dict = {‘runoob’: ‘菜鸟教程’, ‘google’: ‘Google 搜索’}
print “Value : %s” % dict.setdefault(‘runoob’, None)–‘菜鸟教程’
print “Value : %s” % dict.setdefault(‘Taobao’, ‘淘宝’)–‘淘宝’
并查集要解决的问题就是返回连通集的数目,将相互连通的集合进行合并,朋友的朋友也是我的朋友。做法就是如果确定两个节点是连通的,那么不断寻找其根节点,将根节点进行连通,根节点的特点就是其父节点是其本身。
一 模板
有模板可以套:
find函数,找到该节点的根节点;union函数,连接两个节点的根节点。
def UF():
f = {} # f存储的键是节点,值是其父节点;根节点的父节点是本身
s = {} # 记录树的大小,用来平衡,后面有解释。
count = len(XXX) # count指的是节点的个数,起始假设所有节点都是孤立的,所以有count个连通集
def find(x):
f.setdefault(x, x)
while f[x] != x:
x = f[x]
return x # 根节点
def union(x, y): # x和y是有连通关系的两个节点
nonlocal count
root_x = find(x)
root_y = find(y)
if root_x == root_y:
return
f[root_x] = root_y
count -= 1
# 直到把所有的节点的连通关系都消费完了,就返回count
二 树平衡优化
注意,上述可能出现头重脚轻的情况,因为没有区分两个根节点哪个更合适,就直接把一个根节点当作另一个的根节点。所以加一个判断:把小树接到大树的下面,因为大树接小树只会更长,所以加一个记录大小的字典s。
通过比较大小,可以保证树的生长相对平衡,类似平衡二叉树,高度就在logN,所以时间复杂度就是logN,否则最坏情况下可能是N。
def Union_Find():
f = {} # f存储的键是节点,值是其父节点;根节点的父节点是本身
s = {}
count = len(XXX) # count指的是节点的个数,起始假设所有节点都是孤立的,所以有count个连通集
def find(x):
f.setdefault(x, x)
while f[x] != x:
x = f[x]
return x # 根节点
def union(x, y): # x和y是有连通关系的两个节点
nonlocal count
root_x = find(x)
root_y = find(y)
if root_x == root_y:
return
if s.setdefault(root_x, 1) < s.setdefault(root_y, 1):
# setdefault()如果存在,就返回已经存在的,不存在则返回1.
f[root_x] = root_y
s[root_y] += s[root_x] #存储树的大小
else:
f[root_y] = root_x
s[root_x] += s[root_y]
count -= 1
# 直到把所有的节点的连通关系都消费完了,就返回count
三 路径压缩
路径压缩就是把一个节点下面,所带的那些分支都归到这个节点下。这样在寻找根节点的时候,就是O(1)的寻找复杂度。
只需要在搜索根节点的时候顺便加上一句代码,把后面节点的根节点进行前移,这样搜索的时间复杂度大大减小:
def find(x):
while f[x] != x:
f[x] = f[f[x]]
x = f[x]
return x # 根节点
这个操作使得往上搜寻的速度大大减少。可以跳过节点,隔着访问。
这样就压缩结束。
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出:2
解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回 2 。
class Solution(object):
def findCircleNum(self, M):
"""
:type M: List[List[int]]
:rtype: int
"""
count = len(M)
f = {}
s = {}
def Find(x):
f.setdefault(x, x)
while f[x] != x:
f[x] = f[f[x]]
x = f[x]
return x
def Union(x, y):
nonlocal count
root_x = Find(x)
root_y = Find(y)
if root_x == root_y:
return
# setdefault可以直接避免不存在的问题
if s.setdefault(root_x, 1) < s.setdefault(root_y, 1):
f[root_x] = root_y
s[root_y] += s[root_x]
else:
f[root_y] = root_x
s[root_x] += s[root_y]
count -= 1
for i in range(len(M)):
for j in range(len(M[0])):
if M[i][j] == 1:
Union(i, j)
return count
本题构造数据是需要n*n的时间复杂度,两个for循环,但是连通的union取决于find,find的时间复杂度是O(1),所以union的时间复杂度也是O(1).
- 冗余连接
在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
注意!!!nonlocal只用于某个值,字典不需要!!!
# 并查集 可以联系朋友圈这道题
# 解题思路是去掉一条边,如果还是一个连通图则成立
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
def find(x):
f.setdefault(x, x)
while f[x] != x:
f[x] = f[f[x]]
x = f[x]
return x
def Union(x, y, count):
# nonlocal count
root_x = find(x)
root_y = find(y)
if root_x == root_y:
return count
if s.setdefault(root_x, 1) < s.setdefault(root_y, 1):
# 此处易出错!!!!! 注意是根的根变化,不只是x变化!!!
f[root_x] = root_y
s[root_y] += s[root_x]
else:
f[root_y] = root_x
s[root_x] += s[root_y]
count -= 1
return count
for i in range(-1, -len(edges),-1):
f = {}
s = {}
count = len(edges)
list_cp = list(edges)
list_cp.pop(i)
for edge in list_cp:
count = Union(edge[0], edge[1], count)
if len(f) == len(edges) and count == 1:
return edges[i]