最小生成樹 Prim算法和Kuskal算法(Java實現)

照着這個視頻中老師的代碼打了一遍
Prim算法:由頂點確定邊。(鏈接)
簡述一下算法思想:從圖中的任意一個頂點開始,選擇與它相連的權值最小的一條邊,並且還要判斷這條邊是否構成了迴路。下一輪的選擇是判斷所有已經選中的結點中與它們相鄰的結點中權值最下的一條邊,且要求不能構成迴路,直到所有的頂點都被選中了,此時選中的所有邊所構成的圖形就是一顆最小生成樹。

package PrimAlgorithm;

import java.util.Arrays;

public class PrimAlgorithm {

	public static void main(String[] args) {
		//測試看看圖是否創建成功
		char[] data = new char[]{'A','B','C','D','E','F','G'};
		int verxs = data.length;
		//鄰接矩陣的關係使用二維數組表示
		int[][] weight = new int[][]{
			{10000,5,7,10000,10000,10000,2},
			{5,10000,10000,9,10000,10000,3},
			{7,10000,10000,10000,8,10000,10000},
			{10000,9,10000,10000,10000,4,10000},
			{10000,10000,8,10000,10000,5,4},
			{10000,10000,10000,4,5,10000,6},
			{2,3,10000,10000,4,6,10000},			
			};
			
			//創建MGraph對象
			MGraph graph = new MGraph(verxs);
			//創建一個MinTree對象
			MinTree minTree = new MinTree();
			minTree.createGraph(graph, verxs, data, weight);
			//輸出
			minTree.showGraph(graph);
			//測試Prim算法
			minTree.prim(graph, 0);
	}
	}



//創建最小生成樹->村莊的圖
class MinTree {
	//創建圖的鄰接矩陣
	/**
	 * 
	 * @param graph 圖對象
	 * @param verxs 圖對應的頂點個數
	 * @param data 圖的各個頂點的值
	 * @param weight 圖的鄰接矩陣
	 */
	public void createGraph(MGraph graph, int verxs, char data[], int[][] weight) {
		int i, j;
		for(i = 0; i < verxs; i++) {//頂點
			graph.data[i] = data[i];
			for(j = 0; j < verxs; j++) {
				graph.weight[i][j] = weight[i][j];
			}
		}
	}
	
	//顯示圖的鄰接矩陣
	public void showGraph(MGraph graph) {
		for(int[] link : graph.weight) {
			System.out.println(Arrays.toString(link));
		}
	}
	
	//編寫一個Prim算法,得到最小生成樹
	/**
	 * 
	 * @param graph 圖
	 * @param v 表示從圖的第幾個頂點開始生成
	 */
	public void prim(MGraph graph, int v) {
		//visited[]標記結點(頂點)是否被訪問過
		int visited[] = new int[graph.verxs];
		//visited[]默認元素的值都是0,表示沒有訪問過
		
		//把當前結點標記爲已訪問
		visited[v] = 1;
		//h1 和 h2記錄兩個頂點的下標
		int h1 = -1;
		int h2 = -1;
		int minWeight = 10000;//將minWeight初始化爲一個大數,後面遍歷過程中,會被替換
		//因爲有graph.verxs個頂點,Prim算法結束後有graph.verxs-1條邊
		for(int k = 1; k < graph.verxs; k++) {
			
			//這個是確定每一次生成的子圖,和哪個結點的距離最近
			for(int i = 0; i < graph.verxs; i++) {// i結點表示被訪問過的結點
				for(int j = 0; j < graph.verxs; j++) {//j結點表示還沒有訪問過的結點
					if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
						//替換minWeight(尋找已經訪問過的結點和未訪問過的結點間的權值最小的邊)
						minWeight = graph.weight[i][j];
						h1 = i;
						h2 = j;
					}
				}
			}
			//找到一條邊是最小
			System.out.println("邊<" + graph.data[h1] + "," + graph.data[h2] + ">權值:" +minWeight);
			//將當前這個結點標記爲已經訪問
			visited[h2] = 1;
			//minWeight 重新設置爲最大值10000
			minWeight = 10000;
		}
	}
	
	
}

class MGraph {
	int verxs;//表示圖的節點個數
	char[] data;//存放節點數據
	int[][] weight;//存放邊,就是鄰接矩陣
	
	//構造器
	public MGraph(int verxs) {
		this.verxs = verxs;
		data = new char[verxs];
		weight = new int[verxs][verxs];
	}
}

Kuskal算法:由邊確定頂點。
簡述一下算法思想:由圖中權值最小的邊開始,並將選中的這條邊的兩個頂點處理一下,避免出現環;每次選擇權值最小的,且要求這條邊沒有被選中,也沒有構成環的邊,最後選中的邊連接的圖形就是最小生成樹。

package KruskalAlgorithm;

import java.util.Arrays;

public class KruskalAlgorithm {
	private int edgeNum;//記錄邊的個數
	private char[] vertexs;//頂點數組
	private int[][] matrix;//鄰接矩陣
	//使用INF 表示兩個頂點不能連通
	private static final int INF = Integer.MAX_VALUE;
	
	public static void main(String[] args) {
		char[] vertexs = {'A','B','C','D','E','F','G'};
		int[][] matrix = {
				{0,12,INF,INF,INF,16,14},
				{12,0,10,INF,INF,7,INF},
				{INF,10,0,3,5,6,INF},
				{INF,INF,3,0,4,INF,INF},
				{INF,INF,5,4,0,2,8},
				{16,7,6,INF,2,0,9},
				{14,INF,INF,INF,8,9,0}
		};
		//創建KruskalAlgorithm 對象實例
		KruskalAlgorithm kruskalAlgorithm = new KruskalAlgorithm(vertexs, matrix);
		//輸出構建的
		kruskalAlgorithm.print();
		kruskalAlgorithm.kruskal();
	}
	
	//構造器
	public KruskalAlgorithm(char[] vertexs, int[][] matrix) {
		//初始化頂點數和邊的個數
		int vlen = vertexs.length;
		
		//初始化頂點
		this.vertexs = new char[vlen];
		for(int i = 0; i < vertexs.length; i++) {
			this.vertexs[i] = vertexs[i];
		}
		
		//初始化邊,使用的是複製拷貝的方式
		this.matrix = new int[vlen][vlen];
		for(int i = 0; i < vlen; i++) {
			for(int j = 0; j < vlen; j++) {
				this.matrix[i][j] = matrix[i][j];
			}
		}
		//統計邊
		for(int i = 0; i < vlen; i++) {
			for(int j = i+1; j < vlen; j++) {
				if(this.matrix[i][j] != INF) {
					edgeNum++;
				}
			}
		}
	}
	public void kruskal() {
		int index = 0;//表示最後結果數組的索引
		int[] ends = new int[edgeNum];//用於保存"已有最小生成樹"中的每個頂點在最小生成樹的終點
		//創建結果數組,保存最後的最小生成樹
		EData[] rets = new EData[edgeNum];
		
		//獲取圖中所有邊的集合,一共有12條邊
		EData[] edges = getEdges();
		System.out.println("圖的邊的集合=" + Arrays.toString(edges) + "共" + edges.length);
	
		//按照邊的權值大小進行排序(從小到大)
		sortEdges(edges);
		
		//遍歷edges數組,將邊添加到最小生成樹中時,判斷是準備加入的邊是否形成了迴路,如果沒有,就加入rets,否則不能加入
		for(int i = 0; i < edgeNum; i++) {
			//獲取到第i條邊的第一個頂點(起點)
			int p1 = getPosition(edges[i].start);
			//獲取到第i條邊的第2個頂點
			int p2 =getPosition(edges[i].end);
			
			//獲取p1這個頂點在已有最小生成樹的終點
			int m = getEnd(ends, p1);
			//獲取p2這個頂點在已有最小生成樹 中的終點
			int n= getEnd(ends, p2);
			//是否構成迴路
			if(m != n) {//沒有構成迴路
				ends[m] = n;
				rets[index++] = edges[i];//有一條邊加入到rets數組
			}
		}
		
		//統計並打印"最下生成樹",輸出rets
		System.out.println("最小生成樹爲=");
		for(int i = 0; i < index; i++) {
			System.out.println(rets[i]);
		}
	}
	
	//打印鄰接矩陣
	public void print() {
		System.out.println("鄰接矩陣爲:\n");
		for(int i = 0; i < vertexs.length; i++) {
			for(int j = 0; j < vertexs.length; j++) {
				System.out.printf("%12d",matrix[i][j]);//%12d使輸出對齊
			}
			System.out.println();
		}
	}
	//對邊進行排序處理,冒泡排序
	/**
	 * 功能:對邊進行排序處理,冒泡排序
	 * @param edges 邊的集合
	 */
	private void sortEdges(EData[] edges) {
		for(int i = 0; i < edges.length - 1; i++) {
			for(int j = 0; j < edges.length - 1; j++) {
				if(edges[j].weight > edges[j+1].weight) {
					EData tmp = edges[j];
					edges[j] = edges[j+1];
					edges[j+1] = tmp;
				}
			}
		}
	}
	/**
	 * 
	 * @param ch 頂點的值,比如'A','B'
	 * @return 返回ch對應的下標,如果找不到,返回-1
	 */
	private int getPosition(char ch) {
		for(int i = 0; i < vertexs.length; i++) {
			if(vertexs[i] == ch) {//找到
				return i;
			}
		}
		return -1;//找不到
	}
	
	/**
	 * 功能:獲取圖中的邊,放到EData[]數組中,後面我們需要遍歷該數組
	 * 是通過martix 鄰接矩陣來獲取
	 * EData[] 形式[['A','B',12], ['B','F',7], ……]
	 * @return
	 */
	private EData[] getEdges() {
		int index = 0;
		EData[] edges = new EData[edgeNum];
		for(int i = 0; i < vertexs.length; i++) {
			for(int j = i+1; j < vertexs.length; j++) {
				if(matrix[i][j] != INF) {
					edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
				}
			}
		}
		return edges;
	}
	/**
	 * 功能:獲取下標爲i的頂點的終點,用於後面判斷兩個頂點的終點是否相同
	 * @param ends :數組記錄了各個頂點對應的終點是哪個,ends數組是在遍歷過程中逐步形成的
	 * @param i:表示傳入的頂點對應的下標
	 * @return 返回的就是下標爲i的這個頂點的終點的下標
	 */
	
	private int getEnd(int[]ends, int i) {
		while(ends[i] != 0) {
			i = ends[i];
		}
		return i;
	}
}


//創建一個類EData, 它的對象實例就表示一條邊
class EData {
	char start;//邊的一個點
	char end;//邊的另外一個點
	int weight;//邊的權值
	//構造器
	public EData(char start, char end, int weight) {
		this.start = start;
		this.end = end;
		this.weight = weight;
	}
	//重寫toString,便於輸出邊的信息
	@Override
	public String toString() {
		
		return "EData [<" + start + "," + end + "> = " + weight + "]";
		
	}
	
}

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