用鄰接矩陣+普里姆(Prim)算法構造最小生成樹(最小支撐樹)

首先什麼是最小生成樹(我的教材稱爲最小支撐樹)?它是一個圖的極小連通子圖。那什麼是圖?什麼是極小連通子圖

圖是一種數據結構,由頂點集合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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章