1. 程式人生 > >最小生成樹問題(Minimum Spanning Trees, MST)

最小生成樹問題(Minimum Spanning Trees, MST)

最小生成樹問題(Minimum Spanning Trees, MST)

作者:Bluemapleman([email protected])

麻煩不吝star和fork本博文對應的github上的技術部落格專案吧!謝謝大家的支援!

知識無價,寫作辛苦,歡迎轉載,但請註明出處,謝謝!


文章目錄

問題定義

給定一個無向圖G=(V, E),以及邊(Edge)上的權值w: E -> R。

在G中找到一棵生成樹 T E T\subseteq E ,這棵樹連線了所有的頂點,且其所有邊的權值的總和是所有這樣的生成樹中最小的。

生成MST的方法

生成MST的兩個經典演算法Kruskal和Prim演算法都是基於貪心的思想,只是它們應用貪心的方式不太一樣。但是它們都遵循一個一般性方法(Generic Method),這個一般性方法每次為MST找一個邊。這個一般性方法會維持一個邊的集合A,並保持以下迭代不變性質(loop invariant):

每次迭代之後, A是某個MST的邊子集。

每一次迭代過程中,我們確定一個可以加到A中,且不違反迭代不變性質的邊(u,v);也就是說, A ( u , v )

A\bigcup {(u,v)} 也是MST的一個子集,我們叫這樣的邊為安全邊(safe edge),因為我們可以在保持迭代不變性質的前提下,把這個邊安全地加入到A中。

而找安全邊,有以下一個定理可以讓我們參考,

G=(V, E)是一個連通的無向圖,且它的每個邊都有一個相應的權值。A是邊的集合E的一個子集,它是G的最小生成樹的一個部分。讓(S,V-S)稱為G的一個劃分,若有一條權值最小的邊(u,v)橫跨劃分的兩個部分,那麼這個邊就是一個安全邊。

具體證明可參照[1]的23.1節,此處不展開。

而實際的Kruskal與Prim演算法的具體差異,實際上就體現在找安全邊的思路上。

Kruskal

Kruskal的本質是並查集(Union Find)的應用,而並查集的知識可以參考這篇文章並查集(Union-Find)演算法介紹

  • 虛擬碼

pic1

第2、3行:Kruskal首先構造並查集的初始集合,即讓每個頂點都的父親指標都指向自己。

第4行:對G中的所有邊按照權值從小到大進行排序。

第5、6、7行:按照權值從小到大順序遍歷每個邊,如果當前邊的兩個頂點不屬於同一個集合,則將它們連線起來。

  • 示例(來自[1]的23.3節)

pic2

pic3

  • 時間複雜度

    • 對邊按照權值排序: O(ElgE)
    • 聯合與查詢:O(ElgV)

總時長:O(ElgV) (因為 V 1 E C n 2 V 2 V-1\le E \le C_n^2 \le V^2 ,所以lgE=O(lgV),所以O(E(lgE+lgV)=O(ElgV))

Prim

Prim演算法則利用一個優先佇列(Priority Queue)來儲存所有尚未加入到生成樹T中的G的頂點,頂點的優先順序由連線T與該頂點的權重最小的邊的權重決定。

  • 虛擬碼

pic4

第1、2、3行:初始化G中所有除T的起始點之外其它頂點的鍵值(即優先佇列中的優先順序)。並將結點的父親指向空。

第6、7行:開始迴圈,直到優先佇列為空,每次迴圈都從Q中取出優先順序最低的那個頂點。

第8、9、10、11行:遍歷提取出的頂點的每個相鄰邊,看該相鄰邊的權值是否小於邊的另一個頂點的鍵值,若小於,則更新鍵值為邊的權值,並將父親指標指向提取出的頂點。

  • 示例(來自[1]的23.3節)

pic5

  • 時間複雜度

優先佇列操作:

insert(v):V次

extractMin():V次

decreaseKey(v):E次

假設我們用一個二叉堆來實現優先佇列,那麼extractMin()與decreaseKey(v)都將是O(lgV)的複雜度。

總時間:

O(VlgV+ElgV)=O(ElgV) (與Kruskal一樣)

  • *擴充套件

如果用斐波那契堆(Fibonacci Heap,參考[1]第19章)來實現優先佇列,可以實現均攤insert(v)與decreasekey()操作O(1)的時間複雜度。

此時,Prim演算法的總的時間複雜度變成了:O(VlgV+E),比基於二叉堆優先佇列實現的Prim效能更好。

參考文獻

[1] Introduction to Algorithm: Third Edition, Thomas et al.