最小生成樹 Minimum Spanning Tree
在一些路網的構建當中,我們時常期望可以找出一個最簡的路網,使資料可以互相傳遞,而且花費最小。要實現這樣的想法,就會用到最小生成樹的概念
生成樹 Spanning Tree
定義
若一棵樹 是一張連通圖 的子圖,且 (即 擁有所有 的節點),則稱 為一棵生成樹
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
下圖就是上圖的生成樹
性質
因為生成樹 是一棵樹,必須符合樹的性質:
- 無環
- 為一張連通圖
- 任兩節點 只會有一條唯一的路徑
由於一張圖會有多個子圖,而 也是連通圖 的子圖,因此 可能會有很多種
常見的生成樹例子
深度優先搜尋樹 DFS Tree
深度優先搜尋 (DFS) 會訪問圖上的所有節點。事實上將走訪路徑保留後,可以連接所有節點形成一顆生成樹
廣度優先搜尋樹 BFS Tree
相同地,BFS 也後產生一顆生成樹。此生成樹會有一個性質 : 根節點所有其他點的距離為最短
最小生成樹 Minimum Spanning Tree
通常 Minimum Spanning Tree 會簡寫成 MST
樹的權重
一棵樹的權重,就是所有邊權重的和
設一棵樹 ,則用數學式可表達成 :
定義
最小生樹就是所有生成樹當中樹的權重最小的那一棵
根據生成樹的性質,最小生成樹也可能有多種可能
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
右圖就是左圖的最小生成樹
Prim 演算法
想法
Prim 演算法是一個尋找 MST 的貪心演算法。核心概念是想維護一個集合 ,使 最終包含連通圖中所有節點
設一張連通圖
- 在最開始找個起點 裝入 中
- 重複將連接集合 與 的邊中最小權重的那一條所連接的節點 併入 ,直到 時就停止
實作程式碼 (原始版)
- : 鄰接陣列
- : 紀錄節點 是否在集合 中
- : 暫存與節點 所聯的邊權
- : 紀錄每個節點 的父親是誰
- : 節點的數量
- : 邊的數量
實作程式碼 (以 priority queue 優化)
由於整個算法使用 priority queue 寫,感覺會很像 BFS
- : 鄰接陣列
- : 紀錄節點 是否在集合 中
- : 節點的數量
- : 邊的數量
時間複雜度
原始版
總時間複雜度: ,因為雙重迴圈
priority queue 優化版
- :
- :
- 遍歷所有節點 (很像 BFS): ,因為邊數通常比較多,所以取個上限
總時間複雜度:
缺點
挑選不同的起點會影響整個演算法的運作
Kruskal 演算法
想法
Kruskal 演算法是一個尋找 MST 的貪心演算法。核心概念是把邊依照權重由小排到大,再使用併查集維護集合,使其中一個集合最終包含連通圖中所有節點
設一張連通圖
- 設
- 檢查第 小的邊兩終點是否在同一個集合
- 如果不是在同一個集合就將兩集合取聯集
- ++,回到 1. 直到將所有邊都遍歷
實作程式碼
併查集因為之前提過,為了版面大小,這裡就省略實作細節
時間複雜度
- 併查集初始化:
- 查詢所在集合:
- 合併:
- 排序所有邊:
總時間複雜度:
由於邊數最多 條,因此總時間複雜度也可以改寫成:
證明 Prim 演算法與 Kruskal 演算法的正確性
講到這裡,不知有沒有人好奇為什麼這兩種演算法是正確的,難道就不會產生不是生成樹的結果嗎? 接下來要來證明這兩種演算法為什麼是對的
尋找 MST 的通用演算法
首先必須來將 Prim 演算法與 Kruskal 演算法一般化:
先定義 是某個最小生成樹的子集合
以上這段程式每一步都會找出一條邊 ,使 也是某棵 MST 的子集合。我們可以將這種不違反「 是某個最小生成樹的子集合」性質的邊 稱為安全邊
名詞定義
對於一無向圖
- 割線 (cut): 做分割,以 來表示
- 跨越 (cross): 如果一條邊的兩終點分別在 與 之間,就說那條邊跨越割線
- 尊重 (respect): 如果 沒有跨越割線的邊,則稱割線尊重
- 輕邊 (light edge): 跨越割線的邊之中,最輕的那一條稱為輕邊
定理: 安全性
敘述
設 是連通的無向圖,且有一個權重函數 。設 是 的子集合,且 屬於 的某個 MST。設 是在 中尊重 的任何一條割線,且 (u, v) 是跨越 的輕邊,則 對 來說是安全的
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
證明
設 是一個包含 的 MST,則可能會有以下狀況:
- Case1 : 如果 ,則這個東西沒問題
- Case2 : ,則需要找到另一棵 MST ,使
以下說明 是一棵 MST:
因為 是一棵 MST,必存在 -路徑 ,使路徑上至少有一條邊跨越 。設 為任一條這種跨越 的邊。因為割線尊重 ,所以 不在 裡面。將 移除,再將 接回去會產生一顆新的 MST 。總結來說就是以下
接下來,又因為 是一條跨越 的輕邊,因此 ,可推得以下結論
最後需要證明 是安全邊:
因為 且 ,所以 。因為 是 MST,所以 是安全的
由以上證明,可以說明演算法是正確的
題目練習
CSES Road Reparation (裸題)
Zerojudge a129. 最小生成樹 (裸題)
Zerojudge e509. Conscription (最大生成樹??)
Zerojudge f506. 11747 - Heavy Cycle Edges
AtCoder ABC_235 E - MST + 1 (改一下原本的演算法,動動腦)
AtCoder ABC_065 D - Built? (因為貪心的緣故,基本上只需要連最近的 x 座標距離及最近的 y 座標距離)
Zerojudge c495. 四、次小生成樹(SBMST) (需要用到最低共同祖先的原理)
Zerojudge k715. 糧食便道 (Supply)
參考資料
ShanC 程式競賽筆記
作者: ShanC
更新: 2025/1/7