首先什麼是最小生成樹(我的教材稱爲最小支撐樹)?它是一個圖的極小連通子圖。那什麼是圖?什麼是極小連通子圖?
圖是一種數據結構,由頂點集合V(非空)和邊集合E構成,記爲G=(V,E)。G即Graph圖,V即Vertex頂點,E即Edge邊。連通圖可以理解爲能夠一筆畫出來的圖。而極小連通子圖則是包含一個連通圖的所有頂點(假設有n個),卻只有(n-1)條邊(這些邊仍然屬於E)的子圖,並且這個子圖仍然是連通的(如下圖的G1和G2)。
而最小生成樹不僅要是極小連通子圖,並且它邊上的權值之和要最小。顯然,由一個連通圖構造的最小生成樹可能不唯一,但權值之和是唯一的。
這個問題的一個解法是基於貪心思想的Prim算法。設這個圖是G=(V,E),最小生成樹T=(Vt,Et),它的思想是從一個頂點u出發,即初始時T中的Vt={ u }, Et={ },尋找Vt中的頂點與其它頂點(即V-Vt,集合的差運算)構成的邊的最小權值
-------------------------------以下的討論均以上圖爲例(請忽略我的鬼畫符......)-------------------------------
在這個圖中,假設我們從V1點出發(哪個點一樣),可以看到,當前Vt中只有一個頂點{ V1 },從Vt各點到V1,V2,...,V6的最小權值用一個數組low_cost[]表示,約定-1爲結點已經加入Vt,∞表示Vt各點不能到達結點。則數組的值是{-1,6,1,5,∞,∞}。由此可以,下一步我們應當把V3加入Vt,因爲此時Vt的頂點到其餘各點權值最小的便是V3,權值爲1。如此Vt={ V1,V3 },Et={ (V1->V3) }
要注意的是,此時的low_cost[]數組應當進行更新,因爲V3加入Vt,故而而V3到V2的距離是5,比原來的6要小;V3到V4、V5的距離是6、4,比原來的無窮大∞要小,因此low_cost[] = { -1,5,-1,5,6,4 }。
顯然,此時low_cost[]中權值最小(不計-1)的是4,即V6,因此將V6加入Vt,此時Vt={ V1,V3,V6 }, Et={ (V1->V3), (V3->V6) }。同上可知,V6到V4的距離爲2,比原來的6要小,因此low_cost[] = { -1,5,-1,5,2,-1 }。
同理再選取V5加入Vt......
............
如此一直進行到V == Vt爲止,時間複雜度爲O( |V|² ) ,|V|是圖的頂點個數。
以下是源碼和基於以上例子調試結果:
其中代碼部分參考 https://blog.csdn.net/qq_35644234/article/details/59106779
#include <iostream>
#include <string>
#include <vector>
#include <climits>
using namespace std;
typedef struct{
int vexnum; //頂點數
int arcnum; //邊數
int **arc; //鄰接矩陣
string *name;
}Matrix_Graph;
bool newGraph(Matrix_Graph& g)
{
cout << "-------準備使用鄰接矩陣法創建無向帶權圖-------" << endl;
cout << "請輸入頂點數和邊數(空格隔開): ";
cin >> g.vexnum >> g.arcnum;
// 圖可以沒有邊,但不能沒有頂點
// 若無向圖有n個頂點,則最多有n*(n-1)/2條邊(無向完全圖)
if( g.vexnum<0 || g.arcnum<=0 || g.arcnum>(g.vexnum*(g.vexnum-1)/2) ){
cerr << "數據輸入有誤,請檢查數據!" << endl;
g.vexnum = g.arcnum = 0;
return false;
}
// 鄰接矩陣初始化
g.arc = new int*[g.vexnum+1];
g.name = new string[g.vexnum+1];
for(int i = 1; i <= g.vexnum; ++i){
g.name[i] = "V" + to_string(i); //從1開始計數,邊爲V1, V2, V3...
g.arc[i] = new int[g.vexnum+1];
for(int j = 1; j <= g.vexnum; ++j)
g.arc[i][j] = INT_MAX; //各邊距離初始化爲無窮大
}
// 輸入各邊的權值
int vstart, vend, weight;
int n = g.arcnum;
cout << "請輸入" << n << "條邊的起始頂點、終止頂點和權值(空格隔開)" << endl;
while( n-- ){
// 懶,這裏就不做輸入檢查了
cin >> vstart >> vend >> weight;
g.arc[vstart][vend] = g.arc[vend][vstart] = weight;
}
return true;
}
// 該結構體數組存儲當前最小生成樹(Vt)中的頂點到
// 其它頂點(V-Vt)的邊的最小距離weight,-1代表該頂點已經在V中了
// vstart是該邊起點,vend是該邊終點
typedef struct{
int vstart;
int vend;
int weight;
}Closest;
void printRes(vector<string>& Vt, vector<Closest>& Et, Closest *low_cost)
{
cout << "當前最小生成樹的頂點有:";
for(auto v : Vt)
cout << v << ", ";
cout << endl;
cout << "當前最小生成樹的邊:";
for(auto e : Et)
cout << "V"+to_string(e.vstart) << "->" << "V"+to_string(e.vend) << ", ";
cout << endl;
cout << "當前到各結點最小權值:" << endl;
for(int i = 1; i <= low_cost[0].weight; ++i){
cout << "到V" << i << ": ";
if( low_cost[i].weight == INT_MAX )
cout << "∞" << endl;
else
cout << low_cost[i].weight << endl;
}
cout << endl;
}
bool Prim(const Matrix_Graph& g, int start = 1)
{
// 以下2個vector僅爲了輸出過程及結果,對算法無影響
vector<string> Vt; // 存儲當前最小生成樹的結點
vector<Closest> Et;// 存儲當前最小生成樹的邊
int i, j, k;
Closest *low_cost = new Closest[g.vexnum+1];
low_cost[0].weight = g.vexnum; // 記錄頂點數,方便輸出
for(i = 1; i <= g.vexnum; ++i){
low_cost[i].weight = g.arc[start][i];
low_cost[i].vstart = start;
low_cost[i].vend = i;
}
low_cost[start].weight = -1;
Vt.push_back(g.name[start]); // 起始頂點存儲進Vt
printRes(Vt, Et, low_cost);
// 將剩下的 g.vexnum-1 個結點加入Vt中
for(i = 1; i < g.vexnum; ++i)
{
// 查找當前可連接的最小權值邊
int index = 0, min = INT_MAX;
for(j = 1; j <= g.vexnum; ++j){
if( low_cost[j].weight!=-1 && low_cost[j].weight<min ){
min = low_cost[j].weight;
index = j;
}
}
if( index == 0 ){
cerr << "此圖不能形成最小生成樹!" << endl;
return false;
}
// 該邊的weight置-1,表明存儲進V
low_cost[index].weight = -1;
Vt.push_back(g.name[index]);
Et.push_back(low_cost[index]);
// 更新low_cost數組,因爲此時新加入的結點可能
// 比原來的結點到其它結點的權重要小
int newPos = low_cost[index].vend; //新加入結點的編號
for(k = 1; k <= g.vexnum; ++k){
if( g.arc[newPos][k] < low_cost[k].weight ){
low_cost[k].weight = g.arc[newPos][k];
low_cost[k].vstart = newPos;
low_cost[k].vend = k;
}
}
// 打印信息
printRes(Vt, Et, low_cost);
}
cout << "------最小生成樹構造完畢!------" << endl;
return true;
}
int main()
{
Matrix_Graph g;
if( newGraph(g) ){
Prim(g);
}
return 0;
}