Bo2SS

Bo2SS

6 森林与并查集

课程内容#

  • 并查集:合集合 [建立连通关系]、合中的连通关系 [判断某两个点是否连通]

连通性问题#

  • 图片
  • 规则:已经具有连通关系的点不能重复连接

  • 将所有点分成了 2 个集合:①、②

  • [PS] 点与点的关系 (合并、判断连通)→也可以是→集合与集合的关系

Quick-Find 算法#

  • 核心思想:染色
    • 一个颜色,对应一个类别
    • 初始化:个体独立,都写成自己的索引,属于一个独立的集合里
    • ⭐把和自己连通的所有点的颜色改成要染的颜色
  • 时间复杂度
    • 连通判断:O (1)—— 查找快,所以叫 Quick-Find
    • 合并操作:O (N)—— 需要遍历所有的点才能确定是否要合并
  • 🆒引发思考:为什么又叫森林呢?
    • 关键在于合并操作,几个点与几个点的合并,集合与集合的合并
    • 类似于子树与子树之间的合并,或是将一棵树作为另一棵树的子树
    • 所有的集合放在一起,类似于所有的树放在一起,就是森林
  • 那么,除了染色还有其他方式吗?
    • 思考:染成了什么颜色并不重要,只需要知道和哪些点的颜色相同,即连通

Quick-Union 算法#

  • 生活启发:找大哥,找领导
  • 核心思想:找代表元素 [根结点]
    • 存的值就是其代表元素
    • 初始化:个体独立,都写成自己的索引,属于一个独立的集合里
    • ⭐找到两点的代表元素,修改其中一个代表元素里的值为另一个代表元素里的值
    • 代表元素:里面的值就是它自己
    • 联想:大哥或他的小弟叛变了,都从大哥开始叛变,叛变到另一个小弟的大哥那
      • 与 Quick-Find 的结果一样,都是把一棵子树整体合并到另一棵子树,不过是通过代表元素来实现的
      • 只能是根结点指向根结点
  • 时间复杂度
    • 都得找根结点,与树高相关
    • 连通操作:O (树高)
    • 合并操作:O (树高)
    • ❗ 2 种情况
      • 正常状态→均为 O (logN)
      • 退化为一个链表→均为 O (N)
  • 对于退化情况,如何解决呢?👇

weighted Quick-Union 算法#

【按秩合并】

  • 如何避免退化?→保证枝繁叶茂
    • 合并依据 1:树高,矮树挂在高树下 [两两结合]
      • 高度为 h 的树,至少需要的结点个数 N 为 2 ^ (h - 1)
      • 即树高 h = log [2] N + 1 ≈ log [2] N
      • [PS] 只有两棵一样树高的树合并,才会使高度增加
    • 合并依据 2:结点数量,结点少的树挂在结点多的树下
    • 两种优化方式都能得到 O (logN),但是合并依据 2【结点数量】更优秀一些
  • ⭐为什么合并依据 2 更优秀
    • 【示例】什么是平均查找次数
      • 如下图所示,计算了 A、B 树的平均查找次数
      • 图片
      • 结点深度即为结点的查找次数,平均查找次数 = 总查找次数 / 总结点数
      • 此示例,B 树的查找操作更快
    • 【推导】合并依据 2 直接决定平均查找次数
      • 对于有 SA、SB 个结点的 A、B 树,它们的总查找次数 LA、LB 分别为:
        • 图片
        • 其中,li 代表第 i 个结点的深度
      • 此时进行合并操作,分别计算①A→B 和②B→A 的平均查找次数
        • ①当 A 树作为子树合并到 B 树时,为
          • 图片
          • A 树中的所有结点需要多查找一次
        • ②当 B 树作为子树合并到 A 树时,为
          • 图片
          • B 树中的所有结点需要多查找一次
      • ❗【比较两种方式的平均查找次数】
        • 和树高 [LA、LB] 没有直接关系,而分子的结点数量 [SA、SB]【直接】决定查找次数,次数越小越好
        • 👉谁的结点数少,就作为子树被合并
        • ❓思考:上面的推导是否证明高度无法作为合并依据呢?
          • ❌否,高度间接影响着结点数量,一般情况高度越低,结点数量越少
          • 但是,对于特殊情况,A 树比 B 树高,而 A 树结点数量却比 B 树少时,还是按照【结点数量】作为合并依据,将 A 树作为子树合并到 B 树里
    • 所以以结点数作为合并依据更优秀!👇合并思路如下
  • 在合并两棵子树时
    • 如果结点数一样,就按照普通 Quick-Union 的思路换
    • 如果不一样,结点数少的子树的根结点接在👉结点数多的子树的根结点下面
  • [PS] 换句话说
    • 在换大哥时
    • 如果小弟数量一样,就按照普通 Quick-Union 的思路换
    • 如果不一样,小弟少的大哥得跟👉小弟多的大哥混

+ 路径压缩#

  • 参考随堂练习 2 中 weighted Quick-Union 的可视化结果
    • 图片
    • 0 的位置有些尴尬
    • ❗ 不管 0 的代表元素为 1 还是 3,并没区别,将 0 直接挂载 3 的下面,还能减小树高
    • 【方法】让每一个非根结点直接指向根结点,让结构扁平化

上述算法的效率比较

  • 图片

随堂练习#

Quick-Find vs. Quick-Union#

  • 图片
  • 【关键】理解 Quick-Union

    • 0->1->2->4->5、3->4->5;8->9->7->6
    • 查找、合并边界:自己的代表元素就是本身时,停止

Quick-Union vs. weighted Quick-Union#

  • 图片
  • 【关键】理解 weighted 的含义

    • 当两个集合的元素个数不一样时
    • 元素少的集合的代表元素的值👉元素多的集合的代表元素的值
    • 小弟少的大哥得跟着小弟多的大哥混
  • 结果可视化

    • 图片
    • 很明显,weighted 方法得到的树更矮,合并、查找效率更高

代码演示#

HZOJ-71:朋友圈#

图片

样例输入

6 5
1 1 2
2 1 3
1 2 4
1 4 3
2 1 3

样例输出

No
Yes
  • 思路
    • 使用数据结构 —— 并查集
    • 1 即合并操作,2 即查找操作
    • 分别测试 Quick-Find、Quick-Union [+weighted、+ 路径压缩、-weighted] 的算法效率
  • 代码

Quick-Find#

  • 图片
  • 图片
  • 查找、合并策略:基于染色

Quick-Union#

  • 图片
  • 基于 Quick-find

    • 修改结构定义的 color 为代表元素 father
    • 修改查找操作:需要找到底
    • 修改合并操作:也要找到两个元素的底,才合并
  • 易出现退化为链表的问题,下面进行优化

+ weighted#

  • 图片
  • 基于 Quick-Union

  • 添加 size 属性,记录结点所在集合的结点个数,以此作为合并策略

+ 路径压缩#

  • 图片
  • 每次查找就会做一次路径压缩,将【查找元素】到【根代表元素】区间的所有元素都指向根代表元素 [根结点]

-weighted#

  • 图片
  • 抹掉所有与 size 相关的操作
  • 有了路径压缩后,不按秩合并 [去除 size 属性] 也能达到很好的效果

HZOJ 平台上题目测试用时

方法Quick-FindQuick-Union+weighted+ 路径压缩weighted
用时 (ms)7442020164172188
  • Quick-Union 出现了退化问题
  • 加了路径压缩后,不按秩合并也能达到很好的效果
  • 🆒后三个方法都有不错的效果!

思考点#

  • 在 weighted Quick-Union 算法部分,结点数更优秀的推导是否证明高度无法作为合并依据呢?
    • ❌否,高度间接影响着结点数量,一般情况高度越低,结点数量越少
    • 但是,对于特殊情况,A 树比 B 树高,而 A 树结点数量却比 B 树少时
      • 需按照【结点数量】作为合并依据❗ 将 A 树作为子树合并到 B 树里

Tips#

  • 《C++ Primer》建议当工具书使用
  • 图片

课程速记#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。