并查集

Posted beeblog72

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集相关的知识,希望对你有一定的参考价值。

本文参考了【算法】并查集(Disjoint Set)并查集详解

并查集原理

并查集是一种用于处理不相交集合之间合并问题的数据结构,例如求连通子图、判断是否存在环、求最小生成树等。

以判断图中是否有环为例,下图是一个无向图。

graph TB; A---B A---C A---D C---D C---E

首先把每一个顶点都看作是一个集合,逐一地考察每一条边。

  • 考察A---B,则把 A 和 B 放入同一个集合,有{A, B}, {C}, {D}, {E}
  • 考察A---C,则把 A 和 C 放入同一个集合,有{A, B, C}, {D}, {E}
  • 考察A---D,则把 A 和 D 放入同一个集合,有{A, B, C, D}, {E}
  • 考察C---D,发现 C 和 D 已经在同一个集合里了,这就说明图中存在环。

由这个例子可以看出,并查集的主要操作就是判断两个元素是否在同一个集合中,若不在同一个集合中,则将他们合并。实际上,并查集用一个”代表“来表示一个集合,对于给定的任意一个元素,都可以在常数级别的时间复杂度内找到这个元素的”代表“,即所在的集合。

可见,并查集主要包含两种操作:

  1. find(x):找到元素 x 所在集合的代表;
  2. unionSet(x, y): 若 x 和 y 不在同一个集合,则将其合并。

实现(Python)

并查集用树来表示集合,树中的每一个结点就代表集合中的一个元素,树的根结点就是这个集合的”代表“。要注意的是,树中的父子关系并不能代表元素之间的关系。而树是用数组来表示的,索引 i 处存储的是该元素的父结点的索引,而树根结点的父节点就是它本身。首先初始化一个并查集。

def init(n: int) -> List[int]:  # 假设有 n 个元素,即开始时每个元素自成一派
	return [i for i in range(n)]

接下来写find操作。只要每次沿着父节点向上,最终就一定能够找到根结点,即对应集合的”代表“。

def find(x: int, path: List[int]) -> int:
    root = x
	while path[root] != root:
		root = path[root]
	
	return root

最后是合并操作。需要判断两个结点的根结点是否相同,如果不同,则将他们合并。

def unionSet(x: int, y: int, path: List[int]) -> bool:
	x_root = find(x)
	y_root = find(y)
	if x_root == y_root:  # 未发生合并,返回False
		return False
	else:  # 需要合并,返回True
		path[y_root] = x_root
		return True

这时候会发现一个问题,就是之前说的find操作是在常数时间内的,如果构造树的时候构造出一个一字长蛇阵,那么这个操作显然不是常数级别的。因此需要用到路径压缩。

路径压缩

每次查找时,将结点的父节点改为对应集合的根结点。这样每条经过查找的路径高度都会降为 1。但是由于只在查找时进行路径压缩,因此树的结构仍然有可能是奇形怪状的。

只要修改find操作的代码就行了。

def find(x: int, path: List[int]) -> int:
	if x != path[x]:
		path[x] = find(path[x])
	
	return path[x]

按秩合并

另开一个跟path等长的数组,用来存树的高度。每次合并操作时,总是将较矮树的根结点指向较高树的根结点。

def init(n: int) -> List[int]:  # 假设有 n 个元素,即开始时每个元素自成一派
	path = [i for i in range(n)]
	rank = [0 for i in range(n)]
	return path, rank

同时,需要修改合并操作的代码。当两棵树的高度一样时,我们把 y 的根结点指向 x 的根结点。

def unionSet(x: int, y: int, path: List[int]) -> bool:
	x_root = find(x)
	y_root = find(y)
	if x_root == y_root:  # 未发生合并,返回False
		return False
	else:  # 需要合并,返回True
		if rank[x_root] > rank[y_root]:
			path[y_root] = x_root
		elif rank[x_root] < rank[y_root]:
			path[x_root] = y_root
		else:
			path[y_root] = x_root
			rank[x_root] += 1
	
	return True

以上是关于并查集的主要内容,如果未能解决你的问题,请参考以下文章

想要学会并查集吗?看我四十行代码实现它

树--12---并查集

笔记并查集---无向图处理代码模板及类型题

并查集

力扣 每日一题 886. 可能的二分法难度:中等,rating: 1794(并查集 / 拆点优化的扩展域并查集)

并查集