14.7克鲁斯卡尔算法(Kruskal)
Kruskal算法也是求最短路径(最小生成树)的经典算法,一定要掌握,因为在很多的面试题中都会出现
Kruskal算法介绍:
1、 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法
2、 基本思想:按照权值从小到大的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路(注意对比,Prim算法是添加顶点,再从顶点选最小的边,Kruskal是直接选最小的边加入)
3、 具体做法:首先构造一个只含 n 个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森 林中不产生回路,直至森林变成一棵树为止
Kruskal算法实践:
Kruskal算法分析:
Kruskal算法如何判断构成了回路?
package com.atguigu20.kruskal;
import java.util.Arrays;
/**
* @author peng
* @date 2021/12/6 - 20:17
*
* 使用Kruskal算法求最小生成树
*/
public class KruskalCase {
private int edgeNum;//边的个数
private char[] vertexs;//顶点数组
private int[][] matrix;//邻接矩阵
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}
};
KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
kruskalCase.showKruskalCase();
Edata[] edges = kruskalCase.getEdges();
kruskalCase.kruskal();
}
/**
* 初始化
*/
public KruskalCase(char[] vertexs, int[][] matrix) {
this.vertexs = vertexs;
this.matrix = matrix;
//获得边的数量
for (int i = 0; i < matrix.length; i++) {
for (int j = i + 1; j < matrix[0].length; j++) {
if (matrix[i][j] != INF) {
edgeNum++;
}
}
}
}
/**
* 打印二维数组
*/
public void showKruskalCase() {
for (int[] row : matrix) {
System.out.println(Arrays.toString(row));
}
}
/**
* 对边进行一个大小排序
*/
public void sortEdges(Edata[] edges) {
//使用优化版的冒泡排序对边的大小进行排序
boolean flag = false;
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) {
int temp = edges[j].weight;
edges[j].weight = edges[j + 1].weight;
edges[j + 1].weight = temp;
flag = true;
}
}
if (!flag) {
break;
}
flag = false;
}
}
/**
* 输入一点顶点:返回该顶点对应的下标,如果找不到就返回-1
* @param c
* @return
*/
public int getPosition(char c) {
for (int i = 0; i < vertexs.length; i++) {
if (c == vertexs[i]) {
return i;
}
}
return -1;
}
/**
* 定义一个方法:返回存放边的数组{[A,B,12],[C,D,10],...}
*/
public Edata[] getEdges() {
Edata[] edatas = new Edata[edgeNum];//定义一个数组对象,用于保存每一条边的数据,等下可以通过遍历这个数组,实现对边的遍历
//给数组补充边的信息
int index = 0;//edatas数组开始的索引
for (int i = 0; i < vertexs.length; i++) {
for (int j = i + 1; j < vertexs.length; j++) {
if (matrix[i][j] != INF) {
edatas[index++] = new Edata(vertexs[i],vertexs[j],matrix[i][j]);
}
}
}
return edatas;
}
/**
* 该方法是获取一i节点为起点的对应的终点的索引位置,由此来判断是否形成了回路,即是判断能不能添加当前的边
* @param ends:ends数组保存的是,在遍历的过程中每个顶点对应的终点,是实时变化的
* @param i:表示传入的顶点,对应的下标
* @return:返回的是顶点为i的对应的终点的下标
*/
public int getEnd(int[] ends, int i) {
//返回当前结点对应的终点
while (ends[i] != 0) {
//如果当前结点还没有终点,自己就是自己的终点
i = ends[i];
}
return i;
}
/**
* 实现Kruskal算法
*/
public void kruskal() {
int index = 0;//最终结果的索引
int[] ends = new int[edgeNum];//创建一个数组用于保存没有顶点所对应的终点
//最终路的数量是村庄数量 - 1
Edata[] res = new Edata[vertexs.length - 1];//创建边的数组用于保存,最后有哪些边会添加到数组中去,也就是保存最后的结果
//第一步:获取图中的所有的边
Edata[] edges = getEdges();
//第二步:将所有边从小到大进行排序
sortEdges(edges);
//第三步:如果要添加一条边,判断添加该边之后会不会形成回路?
for (int i = 0; i < edgeNum; i++) {
int p1 = getPosition(edges[i].start);//这是第一条边的一个点,假设是A,p1=0
int p2 = getPosition(edges[i].end);//这是第一条边的另一点,假设是B,p2=2
int e1 = getEnd(ends, p1);//这是p1对应的那个顶点,e1=0
int e2 = getEnd(ends, p2);//这是p2对应的那一个顶点,e2=1
//接下来就要判断,p1、p2对应的顶点是不是同一个点,如果他们的终点都是同一个点的话,说明添加这条边之后会形成回路
if (e1 != e2) {
//如果他们的顶点是不同的,说明添加这条边之后不会形成回路
ends[e1] = e2;//添加这条边之后,将p1的终点改为p2的终点,e1=1,表示A的终点是B
res[index++] = edges[i];//将当前这条边加入到最终的结果
}
}
//在这里就获得了最小的生成树,输出最小生成树
for (int i = 0; i < res.length; i++) {
System.out.println(res[i]);
}
}
}
/**
* 创建一个边类,用于保存边的信息
*/
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;
}
@Override
public String toString() {
return "Edata{" +
"start=" + start +
", end=" + end +
", weight=" + weight +
'}';
}
}