帶權值的網圖通常會遇到這樣一個問題:得到一個圖中連通所有頂點的最小權值和,或者求得該最小權值和對應的圖。這樣一類問題在現實生活中也會經常屢見不鮮,比如:現在要在n個村莊之間建立公路,n個村莊最多的情況下可以有n*(n-1)/2 條公路,但是讓這n個村莊間接連通,則連接這n個村莊只需要修建n-1條公路,那麼如何修建這n條公路會使得所花費的代價最小,這個代價可以是公路的最小長度?這一類問題都可以通過求得圖的最小生成樹MST(Minimum cost spanning tree)來實現。
最小生成樹MST:一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊,或者說權值和最優的連通子圖。
求得最小生成樹的算法有兩個Prim和kruskal這裏先介紹prim算法。
prim算法:針對圖的頂點作爲選取單元,生成兩個頂點集合,一個是已經生成的MST集合A,另外一個是沒有被選取到的集合B, A+B=U。主要步驟如下所示:
第一步:開始的時候選取B集合中的任意一個頂點作爲開始頂點,將該頂點V加入到集合A中,因爲MST是針對連通圖進行的,所以從哪個頂點開始並不會影響到最終的計算,因此MST的Prim算法也可以判斷一個圖是否是連通的。
第二步:若集合B不爲空則在集合B中查找和集合A頂點權值最優的下一個頂點,然後將該頂點加入到集合A中,然後跟新集合A到各個頂點的最優權值。
第三步:重複第二步操作,知道集合B中的頂點全部加入到了集合A中,此時集合A中的即爲權值最優的MST。
需要注意的時,在每次選取新節點到A中的時候需要選取和現有的頂點集相連通的多個頂點中連通權值最小的頂點,比如現在A中有V1,v2兩個頂點, B中有V3,V4, v5,而對應的權值爲<v1, v3> = 4, <v2, v4> = 7, <v2, v5> = 3,那麼久選取距離集合v1,v2權值最優的頂點v5,這裏唯一難理解的是集合A的最優權值頂點。這個需要在每次加入新頂點到集合A後進行動態更新。
如下所示:
如上圖一樣,剛開始選取V1,然後找到集合A(v1)最小的頂點v3加入集合A, 此時集合A變成了A(v1, v3), 然後在找到距離集合A(v1,v3)權值最小的頂點v6加入集合A,集合A就變成了A(v1, v3, v6),重複上述過程,直到集合A擁有全部頂點爲止。
代碼說明如下:
#define MaxVer 105
#define MaxInf 0x7FFFFFFF
int visited[MaxVer];
int cost[MaxVer];
int map[MaxVer][MaxVer]; // 使用數組的鄰接矩陣來存儲圖
void init_map(int ver_num)
{
for(int i = 0; i <= ver_num; i++)
{
for(int j = 0; j <= ver_num; j++)
{
if (i == j)
{
map[i][j] = 0;
continue;
}
map[i][j] = MaxInf;
}
}
}
int Prim(int ver_num)
{
int sum = 0; // 記錄最小生成樹權值和
int min_cost, index; // min_cost和index分別記錄單次最小權值和頂點
// 初始化visited和cost數組
for (int i = 1; i <= ver_num; i++)
{
visited[i] = 0;
cost[i] = map[1][i];
}
cost[1] = 0;
visited[1] = 1; // 頂點1開始構建MST,將頂點1標記以添加
for(int i = 2; i <= ver_num; i++)
{
index = -1;
min_cost = MaxInf; // 尋找下一個需要加入MST的頂點
for(int j = 2; j <= ver_num; j++)
{
if (!visited[j] && cost[j] < min_cost)
{
index = j; // 遍歷剩下的頂點,尋找到與當前MST集合距離權值最小的頂點
min_cost = cost[j]; // 並且記錄下對應的權值和頂點index
}
}
if (index == -1 || min_cost == MaxInf)
{
return -1; // 當無法找到下一個最小的權值頂點,有可能此圖不連通
}
sum += min_cost; // 將新找到的權值加入到權值和記錄
cost[index] = 0; // 將新找到的頂掉標記爲已經訪問過
visited[index] = 1; // 將新找到的頂掉標記爲已經訪問過
for (int j = 2; j <= ver_num; j++)
{
if (!visited[j] && map[index][j] < cost[j])
{
cost[j] = map[index][j];
// 遍歷更新當前cost數組,當新加入的頂點到j的距離<index, j>
// 比原始最小距離<1, j>(或者是<x, j> x爲index之前加入MST的頂點)
// 還要最優時, 跟新這個MST集合到剩餘頂點的權值最值,以便下次選取最優頂點
}
}
}
return sum; // 返回最小權值和
}
有上述過程可以看出,prim算法是以圖的頂點起始點,每次選取所有頂點上對應的權值最小的邊進行構建,所以prim的時間開銷和邊無關,對於定點數爲n時prim的時間複雜度爲 O(n^2),所以prim算法更適合求解邊數很多的稠密圖的MST。