克鲁斯卡尔算法介绍
1)克鲁斯卡尔(Kruskal)算法, 是用来求加权连通图的最小生成树的算法。
2)基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
3) 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
克鲁斯卡尔算法公路问题文字描述:
解决公路问题不能构成回路的问题,只需要判断要加入的边的两个顶点是否已经指向同一个终点。
终点:就是将所有顶点从小到大的顺序排列好之后,某个顶点的终点就是与他连通的最大顶点。
package com.kruskal;
import java.util.Arrays;
public class KruskalCase {
private int edgeNum; //边的个数
private char[] verx; //顶点的集合
private int[][] matrix; //邻接矩阵
//使用inf表示两个顶点不连通
private static final int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
char[] verx = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int[][] matrix = {
/*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 kruskal = new KruskalCase(verx,matrix);
kruskal.print();
EData[] edges = kruskal.getEdges();
//未排序
System.out.println(Arrays.toString(edges));
kruskal.sortEdge(edges);
System.out.println("排序后"+Arrays.toString(edges));
kruskal.kruskal();
}
public KruskalCase(char[] verxtexs, int[][] matrix) {
//初始化顶点个数和边的数量
int vLen = verxtexs.length;
//初始化顶点
this.verx = new char[vLen];
for (int i = 0; i < verxtexs.length; i++) {
verx[i] = verxtexs[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();
//按照边的权值从小到大排序
sortEdge(edges);
//遍历edges数组,将边添加到最小生成树中,判断准备加入的边是否形成了回路,如果没有,就加入rets,否则不能加入
for (int i=0;i<edgeNum;i++){
//获取到第i条边的第一个顶点(起点)
int p1 = getPosition(edges[i].start);
//获取到第i条边的第二个顶点
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("打印临接矩阵:");
for (int i=0;i<verx.length;i++){
for (int j=0;j<verx.length;j++){
System.out.printf("%12d",matrix[i][j]);
}
System.out.println();
}
}
/**
* 功能:对边进行排序,使用冒泡排序
* @param edges 边的集合
* */
private void sortEdge(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;
}
}
}
}
/**
* 返回顶点的下标,如果找不到就返回-1
* @param ch 顶点的值,如'A'
* */
private int getPosition(char ch){
for (int i=0;i<verx.length;i++){
if (verx[i] == ch){
return i;
}
}
return -1;
}
/**
* 功能:获取图中边,放到EData[]数组中,后面我们需要遍历该数组
* 是通过matrix临接矩阵来获取
* EData[] 形式:['A','B',12]
* @return
* */
public EData[] getEdges(){
int index = 0;
EData[] edges = new EData[edgeNum];
for (int i=0;i<verx.length;i++){
for (int j=i+1;j<verx.length;j++){
if (matrix[i][j] != INF){
edges[index++] = new EData(verx[i], verx[j], matrix[i][j]);
}
}
}
return edges;
}
/**
* 功能;获取下标i的顶点的终点,用于判断后面两个顶点的
* @param ends 数组就是记录了各个顶点对应的终点是哪个,ends数组在遍历过程中,逐渐形成
* @param i 表示传入的顶点对应的下标
* @return 返回的就是下标为i的这个顶点对应的终点的下标
* */
public 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;
}
@Override
public String toString() {
return "EData{" +
"<" + start +
", " + end +
" > = " + weight +
'}';
}
}