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》建議當工具書使用
  • 圖片

課程速記#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。