最小生成樹(minimum spanning tree,mst)是在乙個給定的無向圖g(v,e)中求一棵樹t,使得這棵樹擁有圖g中的所有頂點,且所有邊都是來自圖g中的邊,並且滿足整棵樹的邊權之和最小。下圖給出了乙個圖g及其最小生成樹t,其中較粗的線即為最小生成樹的邊。可以看到,邊ab、bc、bd包含了圖g的所有頂點,且由它們生成的樹的邊權之和為6,是所有生成樹中權值最小的。
最小生成樹有3個性質需要掌握:
①最小生成樹是樹,因此其邊數等於頂點數減1,且樹內一定不會有環。
②對給定的圖g(v,e),其最小生成樹可以不唯一,但其邊權之和一定是唯一的。
③由於最小生成樹是在無向圖上生成的,因此其根結點可以是這棵樹上的任意乙個結點。於是,,如果題目中涉及最小生成樹本身的輸出,為了讓最小生成樹唯一,一般都會直接給出根結點,讀者只需以給出的結點作為根結點來求解最小生成樹即可。
求解最小生成樹一般有兩種演算法,即prim演算法與kruskal演算法。這兩個演算法都是採用了貪心法的思想,只是貪心的策略不太一樣。
prim演算法(讀者可以將其讀作「普里姆演算法」)用來解決最小生成樹問題,其基本思想是對圖g(v,e)設定集合s,存放已被訪問的頂點,然後每次從集合v-s中選擇與集合s的最短距離最小的乙個頂點(記為u),訪問並加入集合s。之後,令頂點u為中介點,優化所有從u能到達的頂點v與集合s之間的最短距離。這樣的操作執行n次(n為頂點個數),直到集合s已包含所有頂點。可以發現,prim演算法的思想與最短路徑中dijkstra演算法的思想幾乎完全相同,只是在涉及最短距離時使用了集合s代替 dijkstra演算法中的起點s。
①將地圖上的所有邊都抹去,只有當訪問乙個頂點後オ把這個頂點頂點連線的邊顯現(這點和dijkstra演算法中相同)。
②將已訪問的頂點置於ー個巨型防護罩中。可以沿著這個防護罩連線的邊去訪問未到達的頂點
③在地圖中的頂點v(0≤i≤5)上記錄頂點v與巨型防護罩之間的最短距離(即v與每個訪問的頂點之間距離的最小值)。由於在①把所有邊都抹去了,因此在初始狀態下只在頂點v0上標記0,而其他頂點都標記無窮大(記為inf)。為了方便敘述,在下文中某幾處出現的最短距離都是指從頂點v與當前巨型防護罩之間的最短距離。
下面是行動策略:
①由於要訪問六個頂點,因此將②③步驟執行六次,每次訪問乙個頂點(如果是n個頂點,那麼就執行n次)。
②每次都從還未訪問的頂點中選擇與當前巨型防護罩最近的頂點(記為vk(0≤k≤5)),使用「爆裂模式」的能力恢復這條最近的邊(並成為最小生成樹中的一條邊),前往訪問。
③訪問頂點vk後,將vk加入巨型防護罩中,開放地圖上vk連線的所有邊,並檢視以vk作為巨型防護罩連線外界的介面的情況下,能否利用vk剛開放的邊使某些還未訪問的頂點與巨型防護罩的最短距離變小。如果能,則將那個最短距離覆蓋到地圖對應的頂點上。
另外,為了得到最小生成樹的邊權之和,需要在訪問頂點之前設定乙個初值為0的變數sum,並在攻打過程中將加入最小生成樹中的邊的邊權累加起來。
prim演算法解決的是最小生成樹問題,即在乙個給定的無向圖g(v,e)中求一棵生成樹t,使得這棵樹擁有圖g中的所有頂點,且所有邊都是來自圖g中的邊,並且滿足整棵樹的邊權之和最小。prim演算法的基本思想是對圖g(v,e)設定集合s(即巨型防護罩)來存放已被訪問的頂點,然後執行n次下面的兩個步驟(n為頂點個數)。
①每次從集合v-s(即未訪問的頂點)中選擇與集合s(巨型防護罩)最近的乙個頂點(記為u),訪問u並將其加入集合s(加入巨型防護罩),同時把這條離集合s最近的邊加入最小生成樹中。
②令頂點u作為集合s與集合v-s連線的介面(即把當前訪問的頂點作為巨型防護罩與外界的介面),優化從u能到達的未訪問頂點v與集合s(巨型防護罩)的最短距離。
prim演算法的具體實現:
prim演算法需要實現兩個關鍵的概念,即集合s的實現、頂點v(0≤i≤n-1)與集合s(巨型防護罩)的最短距離。
①集合s的實現方法和dijkstra中相同,即使用乙個bool型陣列vis表示頂點是否已被訪問。其中vis[i]=true表示頂點v[i]已被訪問,vis[i]= false則表示頂點v未被訪問。
②不妨令int型陣列d來存放頂點vi(0≤i≤n-1)與集合s(巨型防護罩)的最短距離。初始時除了起點s的d[s]賦為0,其餘頂點都賦為乙個很大的數來表示inf,即不可達。
可以發現,prim演算法與dijkstra演算法使用的思想幾乎完全相同,只有在陣列的含義上有所區別。其中,dijkstra演算法的陣列d含義為起點s到達頂點伍的最短距離,而prim演算法的陣列d含義為頂點vi與集合s的最短距離,兩者的區別僅在於最短距離是頂點i針對「起點s」還是「集合s」。另外,對最小生成樹問題而言,如果僅是求最小邊權之和,那麼在prim演算法中就可以隨意指定乙個頂點為初始點,例如在下面的**中將預設使用0號頂點為初始點。
根據上面的描述,可以得到下面的偽**(注意與prim演算法基本思想進行聯絡):
//g為圖, 一般設定為 全域性變數;陣列d為頂點與集合s的最短距離
prim(g,d)
} }}
和dijkstra演算法的偽**進行比較後發現,dijkstra演算法和prim演算法只有優化d[v]的部分不同,而其他語句都是相同的。這再次說明:dijkstra演算法和prim演算法實際上是相同的思路,只不過是陣列的含義不同罷了。在了解了上面這點之後,讀者可以參照 dijkstra演算法的寫法很容易地寫出prim演算法的**,而在此之前,需要先定義maxv為最大頂點數、inf為乙個很大的數字:
const int maxv = 100; //maxv為最大頂點數
const int inf = 1000000000; //inf是乙個很大的數
下面給出分別使用鄰接矩陣和鄰接表的prim演算法**。
(1)鄰接矩陣版。
int n,g[maxv][maxv]; //n為頂點數,maxv為最大頂點數
int d[maxv]; //頂點與幾個s的最短距離
bool vis[maxv] = ; //標記陣列,vis[i]==true表示已訪問。初始值均為false
int prim(); //標記陣列,vis[i]==true表示已訪問。初始值均為false
int prim(); //標記陣列,vis[i]==true表示已訪問。初始值均為false
int prim()
int ans = prim(); // prim演算法入口
printf("%d\n",ans);
return 0;
}
輸入資料:
6 10
0 1 4
0 4 1
0 5 2
1 2 6
1 5 3
2 3 6
2 5 5
3 4 4
3 5 5
4 5 3
輸出結果:
最小生成樹(prim演算法)
最小生成樹是資料結構中圖的一種重要應用,它的要求是從乙個帶權無向完全圖中選擇n 1條邊並使這個圖仍然連通 也即得到了一棵生成樹 同時還要考慮使樹的權最小。prim演算法要點 設圖g v,e 其生成樹的頂點集合為u。把v0放入u。在所有u u,v v u的邊 u,v e中找一條最小權值的邊,加入生成樹...
最小生成樹 Prim演算法
prim 演算法 以領接矩陣儲存 圖g bool b i 表示頂點i是否被訪問,初始化時候memset b,false,sizeof b b 0 value,表示從第0個節點開始。用value i 表示節點i到最小生成樹a中定點的最小距離。例如value 1 a 0 1 int sum記錄權值和 i...
最小生成樹 prim 演算法
一 演算法描述 假設存在連通帶權圖g v,e 其中最小生成樹為t,首先從圖中隨意選擇一點s屬於v作為起始點,並將其標記後加入集合u 中。然後演算法重複執行操作為在所有v屬於u,u屬於v u的邊 v0,u0 屬於e中找一條代價最小的邊並加入集合t,同時將u0併入u,直到u v為止。這是,t中必有n 1...