圖解最小生成樹 - 普里姆(Prim)算法


我們在圖的定義中說過,帶有權值的圖就是網結構。一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的頂點,但只有足以構成一棵樹的n-1條邊。所謂的最小成本,就是n個頂點,用n-1條邊把一個連通圖連接起來,並且使得權值的和最小。綜合以上兩個概念,我們可以得出:構造連通網的最小代價生成樹,即最小生成樹(Minimum Cost Spanning Tree)。

找連通圖的最小生成樹,經典的有兩種算法,普里姆算法和克魯斯卡爾算法,這里介紹普里姆算法。

為了能夠講明白這個算法,我們先構造網圖的鄰接矩陣,如圖7-6-3的右圖所示。



也就是說,現在我們已經有了一個存儲結構為MGraph的MG(見鄰接矩陣創建圖》)。MG有9個頂點,它的二維數組如右圖所示,數組中我們使用65535代表無窮。

下面我們對着程序和每一步循環的圖示來看:

算法代碼:(改編自《大話數據結構》)

 

 C++ Code 
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 = { } 開始。重復執行下述操作:在所有

uU,v V - U 的邊(u, v) E 中找一條代價最小的邊(u0 , v0) 並入集合TE, 同時v0 並入U, 直至 U = V 為止。此時TE 中必有n-1 條邊, 則 T = (V,{TE} ) 為N的最小生成樹。


由算法代碼中的循環嵌套可得知此算法的時間復雜度為O(n^2)。

對比普里姆和克魯斯卡爾算法,克魯斯卡爾算法主要針對邊來展開,邊數少時效率比較高,所以對於稀疏圖有較大的優勢;而普里姆算法對於稠密圖,即邊數非常多的情況下更好一些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM