「这是我参与2022首次更文挑战的第35天,活动详情查看:2022首次更文挑战」
并查集解决的是连通性的问题,我们在解决问题时找到连接关系是重点。另外并查集是一种高度抽象的数据结构,针对不同的场景,并查集内部的实现可能不一样。
并查集本身针对不同的侧重点也有不同的实现版本。有快速查找、快速合并、合并优化、完整版(包括了路径压缩和合并优化)等等。
快速查找
快速查找中 find 的速度很快。合并的时候要循环遍历,效率比较慢。
class QuickFind {
constructor(n) {
//这里数组的初始化个数是 n + 1,因为有些场景
this.color = new Array(n + 1).fill(0).map((item, index) => index)
}
find(x) {
return this.color(x)
}
merge(a, b) {
const ca = this.color[a]
const cb = this.color[b]
//染色,将颜色 b 染成颜色 a
for (let i = 0; i < this.color.length; i++) {
if (this.color[i] === cb) this.color[i] === ca
}
}
}
快速合并
快速合并中,直接把 b 集合合并到 a 集合,合并的效率很高。但是每次查找都要一级一级往上查,效率不高。
class QuickUnion {
constructor(n) {
//初始化父节点数组,每个节点的父节点默认为自己
this.pa = new Array(n + 1).fill(0).map((item, index) => index)
}
find(x) {
if (this.pa[x] === x) return x
//递归找到父节点
return this.find(this.pa[x])
}
merge(a, b) {
//找到a的根节点
const ra = this.find(a)
//找到b的根节点
const rb = this.find(b)
//将b集合合并到a集合
this.[ra] = rb
}
}
合并优化
把节点数少的集合合并到节点数大的集合当中。因为合并后再做路径优化时,数量少的集合路径优化的次数更少。
class WeightedQuickUnion {
constructor(n) {
//初始化父节点数组,每个节点的父节点默认为自己
this.pa = new Array(n + 1).fill(0).map((item, index) => index)
//初始化每个集合的节点数
this.size = new Array(n + 1).fill(1)
}
find(x) {
if (this.pa[x] === x) return x
//递归找到父节点
return this.find(this.pa[x])
}
merge(a, b) {
//找到a的根节点
const ra = this.find(a)
//找到b的根节点
const rb = this.find(b)
//如果a和b在一个集合中则不需要合并
if (ra === rb) return
//把节点总数小的集合合并到节点总数多的集合里
//更新节点总数多的集合为 a和b之和
if (this.size[ra] < this.size[rb]) {
this.pa[ra] = rb
this.size[rb] += this.size[ra]
} else {
this.pa[rb] = ra
this.size[ra] += this.size[rb]
}
}
}
算法竞赛版
合并优化对性能影响不是太大,而合时的代码比较繁琐,容易出错,所以在竞赛时可以不做优化。
class UnionSet {
constructor(n) {
//初始化父节点数组,每个节点的父节点默认为自己
this.pa = new Array(n + 1).fill(0).map((item, index) => index)
}
get(x) {
//查找x的父节点,并且完成路径优化
//优化后,x的父节点指向所在集合的根节点
return this.pa[x] = this.pa[x] === x ? x : this.get(this.pa[x])
}
merge(a, b) {
//将b集合合并到a集合
this.pa[this.get(a)] = this.get(b)
}
}
完整版
完成版包括了路径压缩和合并优化,性能是做好的。
class UnionSet {
constructor(n) {
//初始化父节点数组,每个节点的父节点默认为自己
this.pa = new Array(n + 1).fill(0).map((item, index) => index)
//初始化每个集合的节点数
this.size = new Array(n + 1).fill(1)
}
get(x) {
//查找x的父节点,并且完成路径优化
//优化后,x的父节点指向所在集合的根节点
return this.pa[x] = this.pa[x] === x ? x : this.get(this.pa[x])
}
merge(a, b) {
//找到a的根节点
const ra = this.get(a)
//找到b的根节点
const rb = this.get(b)
//如果a和b在一个集合中则不需要合并
if (ra === rb) return
//把节点总数小的集合合并到节点总数多的集合里
//更新节点总数多的集合为 a和b之和
if (this.size[ra] < this.size[rb]) {
this.pa[ra] = rb
this.size[rb] += this.size[ra]
} else {
this.pa[rb] = ra
this.size[ra] += this.size[rb]
}
}
}
如有错误欢迎指出,欢迎一起讨论!