最小生成树
在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树。
example:
例如,对于如上图G4所示的连通网可以有多棵权值总和不相同的生成树。
克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法,基于连续的按照最小边的策略进行生成树的构建。Kruskal算法开始的时候把每个顶点都当做棵树,这样就是个森林,然后选取最小的边进行树的连接,最后生成只含有一棵树。
基本思想:将图的存储结构使用边集数组的形式表示,并将边集数组按权值从小到大排序,遍历边集数组,每次选取一条边并判断是否构成环路,不会构成环路则将其加入最小生成树,最终只会包含n-1条边(n为无向图的顶点数)。
具体做法:
- 每次选取最小的边来构成生成树
- 将这条边加入后不能形成闭环
伪代码:
图解:
第1步:将边<E,F>加入R中。
边<E,F>的权值最小,因此将它加入到最小生成树结果R中。
第2步:将边<C,D>加入R中。
上一步操作之后,边<C,D>的权值最小,因此将它加入到最小生成树结果R中。
第3步:将边<D,E>加入R中。
上一步操作之后,边<D,E>的权值最小,因此将它加入到最小生成树结果R中。
第4步:将边<B,F>加入R中。
上一步操作之后,边<C,E>的权值最小,但<C,E>会和已有的边构成回路;因此,跳过边<C,E>。同理,跳过边<C,F>。将边<B,F>加入到最小生成树结果R中。
第5步:将边<E,G>加入R中。
上一步操作之后,边<E,G>的权值最小,因此将它加入到最小生成树结果R中。
第6步:将边<A,B>加入R中。
上一步操作之后,边<F,G>的权值最小,但<F,G>会和已有的边构成回路;因此,跳过边<F,G>。同理,跳过边<B,C>。将边<A,B>加入到最小生成树结果R中。
此时,最小生成树构造完成!它包括的边依次是:<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。
package kruskal;
import java.util.ArrayList;
import java.util.List;
public class Kruskal {
/** 邻接矩阵, 为二维数组, 也称地图 */
private int[][] matrix;
/** 设置最大权重范围, 表示正无穷 */
private int MAX_WEIGHT = Integer.MAX_VALUE;
/** 边集数组, 采用List接口的实现类ArrayList(数组线性表), 遍历元素和随机访问元素的效率比较高 */
private List<Edge> edgeList = new ArrayList<Edge>();
/**
* 创建地图
*/
private void createGraph(int index) { //index表示顶点个数
matrix = new int[index][index]; //邻接矩阵是一个长宽相等的正方形矩阵
//邻接矩阵中每一行的数组, 按照原图以此填充
int[] v0 = { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
int[] v1 = { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
int[] v2 = { MAX_WEIGHT, 18, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
int[] v5 = { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
int[] v6 = { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
int[] v8 = { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
matrix[0] = v0;
matrix[1] = v1;
matrix[2] = v2;
matrix[3] = v3;
matrix[4] = v4;
matrix[5] = v5;
matrix[6] = v6;
matrix[7] = v7;
matrix[8] = v8;
}
/**
* 创建边集数组,并且对他们按权值从小到大排序(顺序存储结构也可以认为是数组吧)
*/
public void createEdges() {
//调用带参构造,此处三个参数分别为begin,end,weight
Edge v0 = new Edge(4, 7, 7);
Edge v1 = new Edge(2, 8, 8);
Edge v2 = new Edge(0, 1, 10);
Edge v3 = new Edge(0, 5, 11);
Edge v4 = new Edge(1, 8, 12);
Edge v5 = new Edge(3, 7, 16);
Edge v6 = new Edge(1, 6, 16);
Edge v7 = new Edge(5, 6, 17);
Edge v8 = new Edge(1, 2, 18);
Edge v9 = new Edge(6, 7, 19);
Edge v10 = new Edge(3, 4, 20);
Edge v11 = new Edge(3, 8, 21);
Edge v12 = new Edge(2, 3, 22);
Edge v13 = new Edge(3, 6, 24);
Edge v14 = new Edge(4, 5, 26);
//同时加入边集数组中(数组线性表)
edgeList.add(v0);
edgeList.add(v1);
edgeList.add(v2);
edgeList.add(v3);
edgeList.add(v4);
edgeList.add(v5);
edgeList.add(v6);
edgeList.add(v7);
edgeList.add(v8);
edgeList.add(v9);
edgeList.add(v10);
edgeList.add(v11);
edgeList.add(v12);
edgeList.add(v13);
edgeList.add(v14);
}
/**
* 克鲁斯卡尔算法
*/
public void kruskal() {
//创建图和边集数组, 该图有v0-v8共9个顶点s
createGraph(9);
//可以由图转出边集数组并按权从小到大排序,这里为了方便观察直接写出来了
createEdges();
//定义一个数组用来判断边与边是否形成环路(并查集知识)
int[] parent = new int[9];
/** 权值总和, 是判断最小生成树的重要依据 , 初始化为0 */
int sum = 0;
int n, m;
//遍历得到每一条边
for (int i = 0; i < edgeList.size(); i++) {
Edge edge= edgeList.get(i);
n = find(parent, edge.getBegin());
m = find(parent, edge.getEnd());
//如果 m == n 说明形成了环路或者两个结点都在一棵树上
//反之,如果 n和m 不等则说明没有闭合线路
if (n != m) {
parent[n] = m; // 设置n在"已有的最小生成树"中的终点为m(就是把n合并在m所在的连通分量中)
System.out.println("(" + edge.getBegin() + "," + edge.getEnd() + ")" +edge.getWeight());
sum += edge.getWeight(); //加上对应边的权重,更新sum值
}
}
System.out.println("权值总和为:" + sum);
}
public int find(int[] parent, int index) {
while (parent[index] > 0) {
//parent[0] = 1表示顶点0,1已经加入生成树中
index = parent[index];
}
return index;
}
public static void main(String[] args) {
Kruskal graph = new Kruskal();
graph.kruskal();
}
}
package kruskal;
/**
* 克鲁斯卡尔->最小生成树边实体
* @author Administrator
*
*/
class Edge {
private int begin; //边的起点
private int end; //边的终点
private int weight; //边的权重
public Edge(int begin, int end, int weight) { //带参构造
super();
this.begin = begin;
this.end = end;
this.weight = weight;
}
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Edge [begin=" + begin + ", end=" + end + ", weight=" + weight + "]";
}
}
代码中所用的图数据如下:
运行结果: