什么是并查集
并查集(Union-find Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。
基本操作
- 初始化建立一个新的并查集,其中包含 n 个单元素集合
- unite(a, b) 把两个元素合并
- findSet(index) 找到某个元素所在的集合
路径压缩
为了加快查找速度,查找时将x到根节点路径上的所有点的parent设为根节点,该优化方法称为压缩路径。
使用该优化后,平均复杂度可视为Ackerman函数的反函数,实际应用中可粗略认为其是一个常数。
代码实现并查集
class UnionFind {
constructor(n) {
// 每个节点初始化父节点为本身
this.parent = new Array(n).fill(0).map((item, index) => index)
// 每个节点上集合的数量,初始化1个本身
this.rank = new Array(n).fill(1)
// 几个个数,初始化为n
this.setCount = n
}
// 快速查找
findSet(index) {
if (this.parent[index] !== index) {
return this.findSet(this.parent[index])
}
return this.parent[index]
}
// 合并
unite(a, b) {
let root1 = this.findSet(a),
root2 = this.findSet(b)
if (root1 !== root2) {
if (this.rank[root1] < this.rank[root2]) {
// 交换
[root1, root2] = [root2, root1]
}
// 小的合并到大的上
this.parent[root2] = root1
// 增加节点上集合数量
this.rank[root1] += this.rank[root2]
//总集合数量减少
this.setCount--
}
}
// 返回集合数量
get count() {
return this.setCount
}
// 两个节点是否连通
connected(a, b) {
let root1 = this.findSet(a),
root2 = this.findSet(b)
return root1 === root2
}
}
算法题中的应用
547. 省份数量
/**
* @param {number[][]} isConnected
* @return {number}
*/
var findCircleNum = function(isConnected) {
var n = isConnected.length
var set = new UnionFind(n) // 新建并查集
for(var i = 0; i < n; i++) {
for(var j = 0; j < n; j++) {
var tmp = isConnected[i][j]
if(i !== j && tmp === 1) set.unite(i, j)
}
}
return set.count // 返回集合的数量
};
684. 冗余连接
/**
* @param {number[][]} edges
* @return {number[]}
*/
var findRedundantConnection = function(edges) {
var n = edges.length
var union = new UnionFind(n) // 新建并查集
var ans = null
for(var i = 0; i < n; i++) {
var tmp = edges[i]
var node1 = tmp[0], node2 = tmp[1]
// 如果边的两个节点都已经在同一个集合中则可以删除当前的边
if(union.findSet(node1) === union.findSet(node2)) ans = tmp
// 合并两个节点
union.unite(node1, node2)
}
return ans
};