一.简介
连通图:任意2节点之间都有路径相通
最小生成树:最小权重生成树
一个 n 结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边(n-1)。 最小生成树可以用prim(普里姆)算法或kruskal(克鲁斯卡尔)算法求出。
二.实现
该图的最小生成树权值和为:19
1.普里姆算法
设T为最小生成树集合,V为节点集合,U为还未放入T集合的节点集合(U=V-T)
1.先选取任意节点放入T
2.获取T 与 U 集合中最小权值节点v’,并加入T集合
3.循环2直到集合T中有n-1条边
2.克鲁斯卡尔算法
按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路(不让新选择边的2节点再次被选择)。
package com.vincent;
import java.util.*;
public class Main {
//定义边
static class Edge{
int vertex1;
int vertex2;
int weight;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edge edge = (Edge) o;
return vertex1 == edge.vertex1 &&
vertex2 == edge.vertex2;
}
@Override
public int hashCode() {
return Objects.hash(vertex1, vertex2);
}
@Override
public String toString() {
return String.format("(%d,%d,%d)",vertex1,vertex2,weight);
}
}
public static void main(String[] args) throws Exception {
char[] datas = {'a','b','c','d','e','f'};
int[][] graph = new int[datas.length][datas.length];
graph[0][1] = 2;
graph[0][2] = 3;
graph[0][5] = 5;
graph[1][0] = 2;
graph[1][3] = 4;
graph[2][0] = 3;
graph[2][4] = 5;
graph[3][1] = 4;
graph[3][5] = 6;
graph[4][2] = 5;
graph[4][5] = 8;
graph[5][3] = 6;
graph[5][4] = 8;
graph[5][0] = 5;
for(int i=0;i<datas.length;i++){
System.out.println(Arrays.toString(graph[i]));
}
System.out.println("------------------");
algOfPrim(graph,datas,0);
System.out.println("------------------");
algOfKruskal(graph,datas);
}
/**
* 普利姆算法
* @param graph 图的邻接矩阵存储,值表示节点之间的权值
* @param datas 对应节点的值
* @param from 最小生成树的开始节点索引
*/
public static void algOfPrim(int[][] graph,char[] datas,int from){
//记录节点是否访问过
int[] book = new int[datas.length];
//保存节点信息
List<Integer> rst = new ArrayList<>();
book[from] = 1;//标记为已访问
rst.add(from);
//n个节点的最小生成树有n-1条边
for(int i=1;i<datas.length;i++){
int weight = Integer.MAX_VALUE;
//记录已选择索引,记录未选择索引,选择索引节点与未选择索引节点组合是当前子集中权值最小的
int minSel = -1,minUnsel = -1;
for(int j=0;j<rst.size();j++){
for(int k=0;k<datas.length;k++){
if(book[k] == 0 && graph[rst.get(j)][k] != 0 && graph[rst.get(j)][k] < weight){
weight = graph[rst.get(j)][k];
minSel = j;
minUnsel = k;
}
}
}
rst.add(minUnsel);
book[minUnsel] = 1;
System.out.printf("%c->%c weight=%d\n",datas[minSel],datas[minUnsel],graph[minSel][minUnsel]);
}
}
/**
* 克鲁斯卡尔算法
* @param graph
* @param datas
*/
public static void algOfKruskal(int[][] graph,char[] datas){
//图转化为Edge结构
List<Edge> edges = new ArrayList<>();
for(int i=0;i<graph.length;i++){
for(int j=i+1;j<graph[i].length;j++){
if(graph[i][j] != 0) {
Edge edge = new Edge();
edge.vertex1 = i;
edge.vertex2 = j;
edge.weight = graph[i][j];
edges.add(edge);
}
}
}
Collections.sort(edges,(a,b)->{
return a.weight-b.weight;
});
System.out.println(edges);
//记录选择的节点
Set<Integer> bookSet = new HashSet<>();
List<Edge> rst = new ArrayList<>();
for(int i=0;i<edges.size();i++){
Edge edge = edges.get(i);
//判断是否有回路(当前边的节点已经包含在记录集合)
if(bookSet.contains(edge.vertex1) && bookSet.contains(edge.vertex2)){
continue;
}
bookSet.add(edge.vertex1);
bookSet.add(edge.vertex2);
rst.add(edge);
}
System.out.println(rst);
}
}
效果:
三.总结
普里姆算法/克鲁斯卡尔算法是解决图的最小联通的有效方法