算法随笔 — 树结构基础 — 并查集

并查集定义

并查集是一种用来解决 连通性 的数据结构,抽象的方向不同会导致实现方式的不同。

我们也可以用并查集来表示集合的关系。

1.快速查找(quick-find)

quick-find
如图所示,我们可以通过给元素加颜色来表示其所属集合,当然颜色只是一种比喻,实际上可以用数字或者其他字符来表示

这种并查集之所以能快速查找是因为查找方法的时间复杂度是O(1)

但是也有一个缺点就是合并比较慢,其合并方法的时间复杂度为O(n)

//* 1.quick-find 快速查找(合并慢)
//* 将属于同一集合的节点标记为同一颜色
class Union_quick_find {
   
  private readonly colors: number[]
  constructor(n: number) {
   
    this.colors = new Array(n).fill(0).map((v, i) => i)
  }
  
  find(index: number) {
   
    return this.colors[index]
  }
  
  merge(a: number, b: number) {
   
    const cb = this.colors[b]
    // 遍历所有元素,将属于b集合的元素归于a集合的名下
    for (let i = 0; i < this.colors.length; i++) {
   
      if (this.colors[i] === cb) this.colors[i] = this.colors[a]
    }
  }
}

从代码实现层面来看,我们能获得的是一个元素的所属 集合

2.快速合并 (quick-union)

顾名思义,这种并查集在合并操作时效率非常高,而查找的效率在一般的实现下是比较不稳定的,当然后面会有相应的优化方法,先介绍快速合并的并查集如何实现

在这种并查集的实现中,我们用一棵树来表示一个集合,用根节点表示一棵树(也就是一个集合)

初始化时,每个节点就是一棵树(不同树代表不同的集合)

//* 2.quick-union 快速合并
//* 将连通关系转换为树形结构,通过递归的方式快速判定
//* 老大通过树的根节点来比较
class Union_quick_union {
   
  private readonly boss: number[]
  constructor(n: number) {
   
    this.boss = new Array(n).fill(0).map((v, i) => i)
  }
  
  find(index: number): number {
   
    if (this.boss[index] === index) return index
    return this.find(this.boss[index])
  }
  
  merge(a: number, b: number) {
   
    const aBoss = this.find(a), bBoss = this.find(b)
    if (aBoss === bBoss) return
    //! 这里没有考虑到两颗树的情况,因此在极端情况下查找效率会非常低
    this.boss[aBoss] = bBoss
  }
}

从以上代码可以发现,查找方法的效率由树高和节点的位置决定,当查找的节点所在树越高、越处在底层,查找效率越慢

同时,上述代码在合并的时候也欠考虑,比如说合并的树比合并前的树高还要高,这样的结果是增加了查找的负担

那是不是就意味着矮的树合并到高的树上就行了呢?其实,我们应该根据树的平均查找次数来判断才是最公正的
树 的 平 均 查 找 次 数 = ∑ 节 点 的 查 找 次 数 节 点 数 树的平均查找次数=\frac{\sum{节点的查找次数}}{节点数} =

节点的查找次数表示一棵树从根节点到目标节点需要走的步数

令 有 a 、 b 两 棵 树 , a 树 有 s a 个 节 点 , 总 共 有 l a 次 查 找 次 数 b 树 有 s b 个 节 点 , 总 共 有 l b 次 查 找 次 数 以 a 为 根 的 合 并 树 的 平 均 查 找 次 数 = l a + l b + s b s a + s b 以 b 为 根 的 合 并 树 的 平 均 查 找 次 数 = l a + l b + s a s a + s b 令有a、b两棵树,a树有s_a个节点,总共有l_a次查找次数 \\ b树有s_b个节点,总共有l_b次查找次数 \\ 以a为根的合并树的平均查找次数=\frac{l_a+l_b+s_b}{s_a+s_b} \\ 以b为根的合并树的平均查找次数=\frac{l_a+l_b+s_a}{s_a+s_b} abasalabsblba=sa+sbla+lb+sbb=sa+sbla+lb+sa
从以上推导出来的两分式可以看出,节点数大的树的根作为合并树的根的话,合并树的查找次数越少

通俗地说就是节点少的当儿子,因此也有了以下的优化代码

//* 3.weighted-quick-union 加权快速合并
//* 通过权重考虑平均查找次数,对合并过程进行优化
//* 权重以节点数量来衡量
class Union_weighted_quick_union {
   
  private readonly boss: number[]
  private readonly size: number[]
  constructor(n: number) {
   
    this.boss = new Array(n).fill(0).map((val, i) => i)
    this.size = new Array(n).fill(0).map(_ => 1)
  }
  
  find(index: number): number
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值