1. 程式人生 > >最小生成樹 Prime演算法

最小生成樹 Prime演算法

問題背景:

對於一個圖,它的所有生成樹中必有一個“邊的權值最小”的生成樹,我們把它稱為最小生成樹。

概念很抽象,換做實際問題:

有十個城市,各個城市之間距離或遠或近。需要建設一個道路網,把十個城市連線在一起,要求道路網的道路長度最小。

各個城市的連線可以抽象為一個圖,本質上即是求該圖的一個最小生成樹。

每一個圖可能有多個生成樹,但最小生成樹 所有邊的權值之和是最小的。

Prime演算法簡介:

最小生成樹和最短路徑一樣,都是在實際中非常常見的圖演算法問題。

同樣也有專門的演算法來構造圖的最小生成樹。

Prime演算法是求解最小生成樹問題最常用的演算法,思想和上次講解的 最短路徑Dijkstra演算法 有些接近。

1.     把所有節點分成兩類,一類是已經加入到了 當前的生成樹中(集合 Y) ,一類是還沒有加入當前的生成樹中(集合N)。

PS:  1. 顯然,這種分類可以用flag來設定。

         2. 最終的生成樹是一步一步構造的,所以說是 “當前的”生成樹。

2.     從 Y 中取得一個節點 Vy, 從 N 中取得一個節點 Vn. 使其滿足 Vy和Vn之間邊的權值最小。

3、 找到滿足上述條件的節點後,把Vn結點從 N 移入 Y中。

Ps: 具體程式碼實現是,flag值修改即可。 (最原始的做法,後續會有改進方式)

3.     重複上述 2. 3. 兩步,直到所有節點全部加入Y中。

具體例子:


程式碼轉化與具體實現:

程式碼實現時需要注意的幾個問題:

1.     如何表示集合Y 和集合 N

當然,可以使用一個flag陣列表示,比如為true表示在Y中,false在N中。

2.     集合Y和集合N中均可能有多個節點,如何從N集合中找到符合條件(距離Y中所有節點最近)的結點?

最粗暴的方法,每次都分別遍歷 Y 和 N集合,兩重遍歷可以找到N中的該結點,但是效率會很差。可以從資料結構設計的角度改善。

建立一個數組, int minDis[ N ]

對於其中元素 minDis[i],其含義是 j 節點到 Y 集合所有節點距離的最小值。

尋找N中符合條件的結點時,只需要遍歷N集合中結點minDis的資料,找到最小值即可。

顯然,對於Y集合中的結點 i ,minDis[i]都是 0.

這樣,minDis陣列也可以替代上步中的flag陣列: minDis值是0,即代表結點在Y集合中。

3.     每次從N集合中找到符合條件的結點 i 後,都需要做哪些處理?

首先,要把它從N移動到Y中。 minDis[i] = 0 即可。(原因參考上步的說明)

其次,要判斷是否需要更新N中剩餘結點 mindDis的值。

因為minDis[j] 儲存的是j結點到Y中所有結點距離的最小值,當把i結點從N移動到Y中後,Y集合元素增多,對於N中剩餘結點j而言,minDis[j]的值可能會減小。

什麼情況下該值會減小?

當從N移動到Y中的結點 I 距離 j結點的值 小於 當前 minDis[j]的值,就需要更新minDis.

參考程式碼:

		#define N 100
		int matrix[N][N];

		//最小生成樹資訊
		int treeLenthResult = 0; 	//最終生成的最小生成樹 邊的權值之和
		int minDis[N];
		int closeVertex[N]; 
		
		//設定起始結點下標是0 (從頂點0開始構造)
		//初始化minDis
		for (int i = 0; i < N; i++) {
			minDis[i] = matrix[i][0];
			closeVertel[i] = 0;
		}
		//起始結點初始化
		minDis[0] = 0;
		closeVertel[0] = 0;

		//N-1次遍歷,每次都從集合N中找到一個結點,距離集合Y中所有結點值最小
		for (int i = 1; i < N; i++) {
			int tmpDis = INF;
			int tmpLoc = -1;
			for (int j = 0; j < N; j++) {
				//minDis[j] != 0  --> j結點不在當前的生成樹中
				if ((minDis[j] != 0) && (minDis[j] < tmpDis) ) {
					tmpDis = minDis[j];
					tmpLoc = j;
				}
			}
			
			if (tmpLoc != -1) {
				//tmpLoc結點 是本輪迴圈中找到的目標結點
				//更新當前得到生成樹邊的權值之和
				treeLenthResult += tmpDis;	
			}

			//tmpLoc結點從N結合移至Y集合
			minDis[tmpLoc] = 0;
			//更新N集合中剩餘的其他結點
			for (int j = 0; j < N; j++) {
				if (minDis[j] > matrix[j][tmpLoc]) {
					minDis[j] = matrix[j][tmpLoc];
					closeVertex[j] = tmpLoc;		//此時,j結點距離Y集合中 tmpLoc結點最近
				}
			}
		}