Java實(shí)現(xiàn)最小生成樹MST的兩種解法
一、prim算法
時間復(fù)雜度較之kruskal較高


通俗的解釋就是:
(1)從哪個點(diǎn)開始生成最小生成樹都一樣,最后的權(quán)值都是相同的
(2)從哪個點(diǎn)開始,先標(biāo)記這個點(diǎn)是訪問過的,用visited數(shù)組表示所有節(jié)點(diǎn)的訪問情況
(3)訪問節(jié)點(diǎn)開始都每個沒訪問結(jié)點(diǎn)的距離選取形成的邊的權(quán)值最小值
綜合以上三點(diǎn)就是我們prim算法寫代碼實(shí)現(xiàn)的重要思路
代碼實(shí)現(xiàn):
package Prim;
import java.util.Arrays;
public class PrimAlgorithm {
public static void main(String[] args) {
//測試看看圖是否創(chuàng)建ok
char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int verxs = data.length;
//鄰接矩陣的關(guān)系使用二維數(shù)組表示,10000這個大數(shù),表示兩個點(diǎn)不聯(lián)通
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 = new MGraph(verxs);
MinTree minTree = new MinTree();
minTree.createGraph(mGraph, verxs, data, weight);
minTree.showGraph(mGraph);
minTree.Prim(mGraph, 0);
}
}
class MinTree {
/**
* 創(chuàng)造圖
* @param graph 圖對象
* @param verxs 圖節(jié)點(diǎn)個數(shù)
* @param data 圖每個頂點(diǎn)的數(shù)據(jù)值
* @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 從哪個節(jié)點(diǎn)開始生成最小生成樹
*/
public void Prim(MGraph graph, int v) {
//定義一個數(shù)組,判斷節(jié)點(diǎn)是不是被訪問過了
int[] visited = new int[graph.verxs];
//v這個點(diǎn)已經(jīng)被訪問了,從這個點(diǎn)開始訪問
visited[v] = 1;
//找到節(jié)點(diǎn)下標(biāo)
int h1 = -1;
int h2 = -1;
int minWeight = 10000;//定義初始值為最大值,只要出現(xiàn)小的就會替換
int sum = 0;
// 從1開始循環(huán),相當(dāng)于就是生成graph.verx - 1條邊
for (int k = 1; k < graph.verxs; k++) {
for (int i = 0; i < graph.verxs; i++) {//遍歷已經(jīng)訪問過的點(diǎn)
if (visited[i] == 1){
for (int j = 0; j < graph.verxs; j++) {//遍歷沒有訪問過的點(diǎn)
//在未訪問點(diǎn)中尋找所有與訪問過的點(diǎn)相連的邊中權(quán)值最小值
if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
}
sum += minWeight; // 求最小生成熟的總權(quán)值
//此時已經(jīng)找到一條邊是最小了
System.out.println("邊<" + graph.data[h1] + "," + graph.data[h2] + "> 權(quán)值:" + minWeight);
//然后標(biāo)記點(diǎn)
visited[h2] = 1;
//將權(quán)值重新變成最大值
minWeight = 10000;
}
System.out.println("最小生成樹的權(quán)值是:" + sum);
}
}
// 圖
class MGraph {
int verxs; // 表示圖節(jié)點(diǎn)個數(shù)
char[] data; // 表示節(jié)點(diǎn)數(shù)據(jù)
int[][] weight; // 表示邊
public MGraph(int verxs) {
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}二、kruskal算法
時間復(fù)雜度低一些,但是代碼量會大一些



對克魯斯卡爾算法的通俗解釋:
(1)對每條邊的權(quán)值進(jìn)行排序
(2)按照從小到大依次選取邊構(gòu)成最小生成樹,但是要注意是否構(gòu)成回路,樹的概念是不能生成回路
(3)此處用的方法比較巧妙使用了getEnd方法來判斷兩者終點(diǎn)是不是一樣,用ends數(shù)組保存最小生成樹中每個頂點(diǎn)的終點(diǎn)
代碼實(shí)現(xiàn):
package Kruskal;
import java.util.Arrays;
public class KruskalCase {
private int edgeNum; //邊的個數(shù)
private char[] vertexs; //頂點(diǎn)數(shù)組
private int[][] matrix; //鄰接矩陣
//使用 INF 表示兩個頂點(diǎn)不能連通
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[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ {0, 12, INF, INF, INF, 16, 14},
/*B*/ {12, 0, 10, INF, INF, 7, INF},
/*C*/ {INF, 10, 0, 3, 5, 6, INF},
/*D*/ {INF, INF, 3, 0, 4, INF, INF},
/*E*/ {INF, INF, 5, 4, 0, 2, 8},
/*F*/ {16, 7, 6, INF, 2, 0, 9},
/*G*/ {14, INF, INF, INF, 8, 9, 0}};
//大家可以在去測試其它的鄰接矩陣,結(jié)果都可以得到最小生成樹.
//創(chuàng)建KruskalCase 對象實(shí)例
KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
//輸出構(gòu)建的
kruskalCase.print();
kruskalCase.kruskal();
}
//構(gòu)造器
public KruskalCase(char[] vertexs, int[][] matrix) {
//初始化頂點(diǎn)數(shù)和邊的個數(shù)
int vlen = vertexs.length;
//初始化頂點(diǎn), 復(fù)制拷貝的方式
this.vertexs = new char[vlen];
for (int i = 0; i < vertexs.length; i++) {
this.vertexs[i] = vertexs[i];
}
//初始化邊, 使用的是復(fù)制拷貝的方式
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];
}
}
//統(tǒng)計邊的條數(shù)
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; //表示最后結(jié)果數(shù)組的索引
int[] ends = new int[edgeNum]; //用于保存"已有最小生成樹" 中的每個頂點(diǎn)在最小生成樹中的終點(diǎn)
//創(chuàng)建結(jié)果數(shù)組, 保存最后的最小生成樹
EData[] rets = new EData[edgeNum];
//獲取圖中 所有的邊的集合 , 一共有12邊
EData[] edges = getEdges();
System.out.println("圖的邊的集合=" + Arrays.toString(edges) + " 共" + edges.length); //12
//按照邊的權(quán)值大小進(jìn)行排序(從小到大)
sortEdges(edges);
//遍歷edges 數(shù)組,將邊添加到最小生成樹中時,判斷是準(zhǔn)備加入的邊否形成了回路,如果沒有,就加入 rets, 否則不能加入
for (int i = 0; i < edgeNum; i++) {
//獲取到第i條邊的第一個頂點(diǎn)(起點(diǎn))
int p1 = getPosition(edges[i].start); //p1=4
//獲取到第i條邊的第2個頂點(diǎn)
int p2 = getPosition(edges[i].end); //p2 = 5
//獲取p1這個頂點(diǎn)在已有最小生成樹中的終點(diǎn)
int m = getEnd(ends, p1); //m = 4
//獲取p2這個頂點(diǎn)在已有最小生成樹中的終點(diǎn)
int n = getEnd(ends, p2); // n = 5
//是否構(gòu)成回路
if (m != n) { //沒有構(gòu)成回路
ends[m] = n; // 設(shè)置m 在"已有最小生成樹"中的終點(diǎn) <E,F> [0,0,0,0,5,0,0,0,0,0,0,0]
rets[index++] = edges[i]; //有一條邊加入到rets數(shù)組
}
}
//<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。
//統(tǒng)計并打印 "最小生成樹", 輸出 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]);
}
System.out.println();//換行
}
}
/**
* 功能:對邊進(jìn)行排序處理, 冒泡排序
*
* @param edges 邊的集合
*/
private void sortEdges(EData[] edges) {
for (int i = 0; i < edges.length - 1; i++) {
for (int j = 0; j < edges.length - 1 - i; j++) {
if (edges[j].weight > edges[j + 1].weight) {//交換
EData tmp = edges[j];
edges[j] = edges[j + 1];
edges[j + 1] = tmp;
}
}
}
}
/**
* @param ch 頂點(diǎn)的值,比如'A','B'
* @return 返回ch頂點(diǎn)對應(yīng)的下標(biāo),如果找不到,返回-1
*/
private int getPosition(char ch) {
for (int i = 0; i < vertexs.length; i++) {
if (vertexs[i] == ch) {//找到
return i;
}
}
//找不到,返回-1
return -1;
}
/**
* 功能: 獲取圖中邊,放到EData[] 數(shù)組中,后面我們需要遍歷該數(shù)組
* 是通過matrix 鄰接矩陣來獲取
* 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;
}
/**
* 功能: 獲取下標(biāo)為i的頂點(diǎn)的終點(diǎn)(), 用于后面判斷兩個頂點(diǎn)的終點(diǎn)是否相同
*
* @param ends : 數(shù)組就是記錄了各個頂點(diǎn)對應(yīng)的終點(diǎn)是哪個,ends 數(shù)組是在遍歷過程中,逐步形成
* @param i : 表示傳入的頂點(diǎn)對應(yīng)的下標(biāo)
* @return 返回的就是 下標(biāo)為i的這個頂點(diǎn)對應(yīng)的終點(diǎn)的下標(biāo), 一會回頭還有來理解
*/
private int getEnd(int[] ends, int i) { // i = 4 [0,0,0,0,5,0,0,0,0,0,0,0]
while (ends[i] != 0) {
i = ends[i];
}
return i;
}
}
//創(chuàng)建一個類EData ,它的對象實(shí)例就表示一條邊
class EData {
char start; //邊的一個點(diǎn)
char end; //邊的另外一個點(diǎn)
int weight; //邊的權(quán)值
//構(gòu)造器
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 + "]";
}
}到此這篇關(guān)于Java實(shí)現(xiàn)最小生成樹MST的兩種解法的文章就介紹到這了,更多相關(guān)Java最小生成樹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java模擬http的Get/Post請求,并設(shè)置ip與port代理的方法
下面小編就為大家?guī)硪黄猨ava模擬http的Get/Post請求,并設(shè)置ip與port代理的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
Java實(shí)現(xiàn)經(jīng)典游戲黃金礦工的示例代碼
《黃金礦工》游戲是一個經(jīng)典的抓金子小游戲,它可以鍛煉人的反應(yīng)能力。本文將用Java實(shí)現(xiàn)這一經(jīng)典的游戲,感興趣的小伙伴可以了解一下2022-02-02
spring?kafka?@KafkaListener詳解與使用過程
這篇文章主要介紹了spring-kafka?@KafkaListener詳解與使用,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02
使用@SpringBootTest注解進(jìn)行單元測試
這篇文章主要介紹了使用@SpringBootTest注解進(jìn)行單元測試,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
java中public class與class的區(qū)別詳解
以下是對java中public class與class的區(qū)別進(jìn)行了分析介紹,需要的朋友可以過來參考下2013-07-07
Java使用正則表達(dá)式實(shí)現(xiàn)找出數(shù)字功能示例
這篇文章主要介紹了Java使用正則表達(dá)式實(shí)現(xiàn)找出數(shù)字功能,結(jié)合實(shí)例形式分析了Java針對數(shù)字的匹配查找及非數(shù)字替換操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-03-03

