我們在圖的定義中說過,帶有權值的圖就是網結構。一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的頂點,但只有足以構成一棵樹的n-1條邊。所謂的最小成本,就是n個頂點,用n-1條邊把一個連通圖連接起來,並且使得權值的和最小。綜合以上兩個概念,我們可以得出:構造連通網的最小代價生成樹,即最小生成樹(Minimum Cost Spanning Tree)。
找連通圖的最小生成樹,經典的有兩種算法,普里姆算法和克魯斯卡爾算法,這里介紹普里姆算法。
為了能夠講明白這個算法,我們先構造網圖的鄰接矩陣,如圖7-6-3的右圖所示。

也就是說,現在我們已經有了一個存儲結構為MGraph的MG(見《鄰接矩陣創建圖》)。MG有9個頂點,它的二維數組如右圖所示,數組中我們使用65535代表無窮。
下面我們對着程序和每一步循環的圖示來看:
算法代碼:(改編自《大話數據結構》)
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
/* Prim算法生成最小生成樹 */ void MiniSpanTree_Prim(MGraph MG) { int min, i, j, k; int adjvex[MAXVEX];/* 保存相關頂點下標 */ int lowcost[MAXVEX];/* 保存相關頂點間邊的權值 */ lowcost[0] = 0;/* 初始化第一個權值為0,即v0加入生成樹 */ /* lowcost的值為0,在這里就是此下標的頂點已經加入生成樹 */ adjvex[0] = 0;/* 初始化第一個頂點下標為0 */ cout << "最小生成樹的邊為:" << endl; for (i = 1; i < MG.numVertexes; i++) { lowcost[i] = MG.arc[0][i];/* 將v0頂點與之有邊的權值存入數組 */ adjvex[i] = 0;/* 初始化都為v0的下標 */ } for (i = 1; i < MG.numVertexes; i++) { min = INFINITY; /* 初始化最小權值為∞, */ j = 1; k = 0; while (j < MG.numVertexes)/* 循環全部頂點 */ { if (lowcost[j] != 0 && lowcost[j] < min)/* 如果權值不為0且權值小於min */ { min = lowcost[j];/* 則讓當前權值成為最小值 */ k = j;/* 將當前最小值的下標存入k */ } j++; } cout << "(" << adjvex[k] << ", " << k << ")" << " "; /* 打印當前頂點邊中權值最小的邊 */ lowcost[k] = 0;/* 將當前頂點的權值設置為0,表示此頂點已經完成任務 */ for (j = 1; j < MG.numVertexes; j++)/* 循環所有頂點 */ { /* 如果下標為k頂點各邊權值小於此前這些頂點未被加入生成樹權值 */ if (lowcost[j] != 0 && MG.arc[k][j] < lowcost[j]) { lowcost[j] = MG.arc[k][j];/* 將較小的權值存入lowcost相應位置 */ adjvex[j] = k;/* 將下標為k的頂點存入adjvex */ } } } cout << endl; } |
1、程序中1~16行是初始化操作,其中第7~8行 adjvex[0] = 0 意思是現在從頂點v0開始(事實上從那一點開始都無所謂,假定從v0開始),lowcost[0]= 0 表示v0已經被納入到最小生成樹中,之后凡是lowcost數組中的值被設為0就表示此下標的頂點被納入最小生成樹。
2、第11~15行表示讀取鄰接矩陣的第一行數據,所以 lowcost數組為{ 0 ,10, 65535, 65535, 65535, 11, 65535, 65535, 65535 },而adjvex數組為全0。至此初始化完畢。
3、第17~49行共循環了8次,i從1一直累加到8,整個循環過程就是構造最小生成樹的過程。
4、第24~33行,經過循環后min = 10, k = 1。注意26行的if 判斷lowcost[j] != 0 表示已經是生成樹的頂點則不參加最小權值的查找。
5、第35行,因k = 1, adjvex[1] = 0, 所以打印結果為(0, 1),表示v0 至 v1邊為最小生成樹的第一條邊,如下圖的第一個小圖。
6、第36行,因k = 1 將lowcost[k] = 0 就是說頂點v1納入到最小生成樹中,此時lowcost數組為{ 0,0, 65535, 65535, 65535, 11, 65535, 65535, 65535 }
7、第38~47行,j 循環從1 到8, 因k = 1,查找鄰接矩陣的第v1行的各個權值,與lowcost數組對應值比較,若更小則修改lowcost值,並將k值存入adjvex數組中。所以最終lowcost = { 0,0, 18, 65535, 65535, 11, 16, 65535, 12 }。 adjvex數組的值為 {0, 0, 1, 0, 0, 0, 1, 0, 1 }。這里的if判斷也表示v0和v1已經是生成樹的頂點不參與最小權值的比對了。
上面所述為第一次循環,對應下圖i = 1的第一個小圖,由於要用文字描述清楚整個流程比較繁瑣,下面給出i為不同值一次循環下來后的生成樹圖示,所謂一圖值千言,大家對着圖示自己模擬地循環8次就能理解普里姆算法的思想了。

即最小生成樹的邊為:(0, 1), (0, 5), (1, 8), (8, 2), (1, 6), (6, 7), (7, 4), (7, 3)
最后再來總結一下普里姆算法的定義:
假設N = (V{E} )是連通網,TE是N上最小生成樹的集合。算法從U = { u0} ( uo
V),TE = { } 開始。重復執行下述操作:在所有
u
U,v
V - U 的邊(u, v)
E 中找一條代價最小的邊(u0 , v0) 並入集合TE, 同時v0 並入U, 直至 U = V 為止。此時TE 中必有n-1 條邊, 則 T = (V,{TE} ) 為N的最小生成樹。
對比普里姆和克魯斯卡爾算法,克魯斯卡爾算法主要針對邊來展開,邊數少時效率比較高,所以對於稀疏圖有較大的優勢;而普里姆算法對於稠密圖,即邊數非常多的情況下更好一些。
