1. 程式人生 > >圖的最小生成樹prim演算法詳解

圖的最小生成樹prim演算法詳解

prim演算法是求圖的最小生成樹的一種演算法,它是根據圖中的節點來進行求解,具體思想大概如下:
首先,將圖的所有節點(我們假定總共有n個節點)分成兩個集合,V和U。其中,集合V儲存的是我們已經訪問過的節點,集合U儲存的是我們未曾訪問的節點。prim演算法第一步就是選定第一個節點放入集合V中(一般是第一個),然後查詢V中節點和U中節點之間距離最小的那個路徑,(假設U中節點w離V中的某個節點距離最近),將w放入V中,將w從U中刪除,這樣,直到U中所有節點都被放入V中,那麼我們就找到了最小生成樹。
因為prim演算法每一步都需要遍歷集合V和集合U中的所有節點,其時間複雜度為O(n*n),這是未經任何優化的prim演算法時間複雜度。
一般,我們要對該演算法進行優化,我們維護一個數組low[n],用來儲存到每個節點的已知的最短路徑是多少,例如,我們有圖如下:這裡寫圖片描述


首先,我們選定v1作為我們的初始節點,這時,low陣列的大小分別為:
low[1] = 0;
low[2] = 6;
low[3] = 1;
low[4] = 5;
low[5] = INF;
low[6] = INF;
因為5和6從1無法到達,所以設定為無窮大。
此時,集合V中有節點{V1},集合U中有節點{V2,V3,V4, V5,V6},此時,通過條件判斷,我們可以找到節點3離V1最近(通過low陣列),然後,我們將節點V3放入集合V中,並從U中刪除它,此時
集合V= {V1,V3}
集合U={V2,V4,V5,V6}
這時,我們需要對low陣列進行更新。前面講過,low陣列是儲存的我們已知的點到剩餘節點的最短距離,因為此時V3加入了已知點集,所以需要根據V3到其它節點的距離進行更新;
low[1] = 0;//因為節點V1已經被訪問過,所以不需要更新
low[2] = 5;//因為從節點V3到節點V2的距離為5,比上一個資料low[2] = 6要小,因此需要更新;
low[3] = 1;//因為節點V3已經被訪問過,所以不需要更新;
low[4] = 5;//因為從節點V3到V4的距離和上一個資料low[4]=5一樣,我們不更新;
low[5] = 6;//很明顯,此時V3到V5的距離要比上一個資料low[5]=INF來的小,我們進行更新;
low[6] = 4;//道理同low[5];
此時,我們對low陣列進行更新完畢,這樣,下次就可以繼續通過low陣列來查詢最距離最短的那個點。
prim核心程式碼如下:

#include <iostream>
using namespace std;

void prim()
{
    const int INF = 65536;//表示無窮大
    const int N = 100;//N為圖中所有的節點數
    bool visited[N+1] = {false};//陣列visited儲存節點是否有被訪問過,初始化為false
    int low[N+1];//儲存已訪問節點離未訪問節點的最短距離
    int dist[N+1][N+1];//距離矩陣,用來儲存圖中節點之間的距離
    int result = 0;//最短距離
    int edge[N+1
];//用來儲存選擇的點的依附節點 int min = INF; int v; //////////////////初始化矩陣///////////////////////// for (int i=1; i<=N; ++i) { for (int j=1; j<=N; ++j) { if (i == j) { dist[i][j] = 0; } else dist[i][j] = INF; } } //////////////////輸入邊的資訊////////////////////// int m, a, b, c; cout << "輸入總的邊數:"; cin >> m; cout << "輸入邊的兩個節點和邊的長短:" << endl; for (int i=1; i<=m; ++i) { cin >> a >> b >> c; dist[a][b] = dist[b][a] = c; } ////////////////////////////////////////////////// visited[1] = true;//我們選取第一個節點為初始節點 low[1] = 0; edge[1] = 1; //初始化low和edge for (int i=2; i<=N; ++i) { low[i] = dist[1][i]; edge[i] = 1;//初始化所有的節點依附節點為節點1 } //迴圈N-1次,來求得最小生成樹所花費的最短路徑,因為有N個點,所以樹有N-1個邊 for (int i=1; i<N; ++i) { min = INF; for (int j=1; j<=N; ++j) { if (!visited[j] && low[j]<min)//如果節點j沒有被訪問過並且當前路徑小於min { min = low[j]; v = j; } } //找到未被訪問的節點V路徑最短 result += min; visited[v] = true; cout << "(" << v << "," << edge[v] << ")" << endl;//輸出找到的這個點所依附的節點,相當於輸出一條邊 //更新陣列low和edge for (int i=1; i<=N; ++i) { if (!visited[i] && low[i]>dist[v][i])//如果節點i沒有被訪問過並且當前路徑小於已知的路徑low { low[i] = dist[v][i]; edge[i] = v;//將節點i的新依附節點設定為找到的節點v } } } cout << result << endl; return ;//完畢後,得到的result即為最小生成樹最短路徑長度,陣列edge即為選擇的節點邊的資訊 } int main() { prim(); system("pause"); return 0; }

若有錯誤,歡迎指出。另外,我們假定節點編號從1開始,而不是從0開始。