最小生成樹的形成
(1)一個貪心策略設計如下
每個時刻生長最小生成樹的一條邊,並在整個策略的實施過程中,遵守下述循環不變式的邊集合A:
每一步,選擇一條邊(u,v)加入集合A,使得A不違反循環不變式。
這樣的邊使得我們可以“安全地”將之加入到集合A而不會破壞A的循環不變式,因此稱之為集合A的“安全邊”。
(2)使用的循環不變式方式如下:
初始化:集合A直接滿足循環不變式。
保持:算法的循環通過只加入安全邊來維持循環不變式。
終止:所有加入到集合A中的邊都屬於某棵最小生成樹,因此,算法第5行所返回的集合A必然是一棵最小生成樹。
說明:循環不變式告訴了我們存在一棵生成樹,滿足。在進入while循環時,A是T的真子集,因此必然存在一條邊
,使得
,並且(u,v)對於集合A是安全的。
(3)定義一些概念
切割:無向圖G=(V,E)的一個切割(S,V-S)是集合V的一個划分。
橫跨切割:如果一條邊(u,v)∈E的一個端點在集合S中,另一個端點在集合V-S中,則稱該條邊橫跨切割(S,V-S)。
尊重集合:如果集合A中不存在橫跨該切割的邊,則稱該切割尊重集合A。
輕量級邊:在橫跨一個切割的所有邊中,權重最小的邊稱為輕量級邊
(4)距離說明上述概念
圖(a)中所示的黑色結點位於集合S中,白色結點位於V-S中。橫跨該切割的邊是那些連接白色結點和黑色結點的邊。如<a,h><b,c><c,d>等等,其中<d,c>是輕量級邊。若定義加了陰影的邊屬於集合A,那么可以看出集合A中沒有橫跨切割的邊,所以切割<S,V-S>尊重集合A。
圖(b)中是同樣一個圖,只是換了視角。
(5)辨認安全邊的規則
設G=(V,E)是一個在邊E上定義了實數值權重函數ω的連通無向圖。設集合A為E的一個子集,且A包含在圖G的某棵最小生成樹中,設(S,V-S)是圖G中尊重集合A的任意一個切割,
又設(u,v)是橫跨切割(S,V-S)的一條輕量級邊。那么邊(u,v)對於集合A是安全的。
證明:
我們現在構建一個最小生成樹T ′,通過將A∪{(u,v)}包括在樹T '中,從而證明(u,v)對集合A來說是安全的。T中包含有G的所有結點,所以(u,v)與T中從結點u到結點v的簡單路徑p形成一個環。如上圖所示。由於結點u,v分別處於切割S,V-S中,T中至少含有一條簡單路徑p並且橫跨該切割。假設(x,y)是這樣一條邊。由於切割(S,V-S)尊重集合A,所以(x,y)不在集合A中。又因為(x,y)位於T中從u到v的唯一一條簡單路徑上,將這條邊刪除會導致T被分為兩個連通分量,這時候,將(u,v)加入就能將這兩個連通分量連接起來成為一顆心得生成樹。
這時候我們來證明T ′是一顆最小生成樹。
由於(u,v)是橫跨切割的一條輕量級邊,並且邊(x,y)也橫跨該切割,所以我們有ω(u,v)≤ ω(x,y),所以可以簡單得出這樣一個關系
又因為T是一顆最小生成樹,ω(T) ≤ ω(T '),因此T ′也是一顆最小生成樹。
因為 A∈T,且 (x,y)∉A,所以A∈T ' 。因此。由於T ' 是最小生成樹,(u,v)對於集合A而言是安全的。
在算法GENERIC-MST推進的過程中,集合A始終保持無環狀態( 每條邊都是安全的 )。算法執行的任意時刻,圖G A =(V,A)是一個森林。
while循環執行|V|-1次,每次找出構造最小生成樹所需的一條邊。每遍循環將樹的數量減少1。當整個森林只含有一棵樹時,算法終止。
Kruskal算法
Kruskal和Prim算法是求解最小生成樹的兩個經典算法。它們都是GENERIC-MST算法的具體細化:
(1)Kruskal算法找安全邊的方法:在所有連接森林中兩棵不同樹的邊中,找權重最小的邊(u,v)。設C 1 和C 2 為邊(u,v)所連接的兩棵樹。邊(u,v)是C 1 的一條安全邊。
(2)簡單講述一下Kruskal算法的工作過程。算法的1~3行將集合A初始化為一個空集,並創建|V|棵樹,每棵樹僅包含一個結點,作為初始情況。5~8行中的for循環按照權重從低到高的次序對每一條邊逐一進行檢查。對於每條邊(u,v)而言,循環將檢查該結點u和結點v是否屬於同一棵樹。如果是,這條邊不能加入,避免形成環路。如果不是,則兩個端點分別屬於不同的樹,算法第7行將吧這些邊加入到集合A中,第8行將兩棵樹進行合並。
(3)下面給出一個具體的例子看看算法的流程
①首先找到最小權重的邊為<h,g> = 1
②繼續尋找下一條權重次小的邊,以此類推
③注意到,(f)圖中此時權重為6的邊的兩個端點i和g都屬於同一棵樹(<i,c,f,g,h>)中,所以不能加這條邊加入,否則會形成環路
④同③一樣,(h)圖中的權重為7的邊不同加入集合中,避免形成環路(<i,c,f,g,h>)
⑤同④一樣,(j)圖中的權重為8的邊不同加入集合中,避免形成環路(<a,b,d,c,f,g,h>)
⑥同理,<e,f>邊如果加入集合中,會形成環路<c,d,e,f>;<b,h>不能加入集合中,會形成<a,b,h>環路;<d,f>不能加入集合中,會形成<c,f,d>環路
Prim算法
(1)Prim算法的每一步是在連接集合A和A之外的結點的所有邊中,選擇一條輕量級邊加入到A中。所加入的邊對於A也是安全的。Prim算法的基本性質:集合A中的邊總是構成一棵樹。
(2)簡單描述一下上述算法的流程。1~5行將每個節點的key值設置為∞(除根節點r以外,根節點key值置為0,作為第一個被處理的點),然后將每個結點的父節點置為NIL,並對最小優先隊列Q進行初始化,使其包含圖中的所有節點。
算法維持的循環不變式由3個部分組成:
①A={(v,v.π):v∈V-{r}-Q}
②已經加入到最小生成樹中的結點集合為V-Q
③對於所有結點v∈Q,如果v.π≠NIL,則v.key<∞並且v.key是連接結點v和最小生成樹中某個節點的輕量級邊(v,v.π)的權重
第7行找出結點v∈Q,該結點是某條橫跨切割<V-Q,Q>的輕量級邊的一個端點,然后將結點u從隊列中刪除,並將其加入到集合V-Q中,也就是將邊<u,u.π>加入到集合A中。8~11行的for循環將每個與u鄰接但是不在樹中的結點v的key和π屬性進行更新,從而維持循環不變式。
(3)簡單看一個Prim算法的例子
例子中,初始的根節點為a,加陰影的邊和黑色結點屬於樹A。在算法的每一步,樹中的結點就決定了圖的一個切割,橫跨該切割的一條輕量級邊就被加入到樹中。
例如:在圖(b),輕量級邊有兩條,<b,c>,<a,h>的權重都為8,所以可以選擇兩條邊中的一條加入到樹中