克鲁斯卡尔之公交车站问题详解
说明
- 克鲁斯卡尔算法也是解决图的最短路径问题,即给定一个带权的无向图,求其最小生成树使得各个顶点之间的距离最短
- 公交车站问题是指有七个公交车站,修一条通路连接这七个公交车站,使得路劲最短
- 可以使用带权的无向图来模拟公交车站问题,因此可以使用克鲁斯卡尔算法解决
- 克鲁斯拉尔算法思路???
- 先求出各个顶点之间的路径,即权值,将这些路径按权值按照从小到大排序
- 然后依次获取每条路径的两个顶点,从权值最小的开始,将对应的顶点连接,但是在连接前,要先判断当前两个顶点的连接是否会构成通路,如果构成通路,则再判断下一条路径
- 判断是否构成通路的方法: 可以根据已连接最小生成树的终点判断,即判断要连接的两个顶点的终点是否是同一个顶点,如果是则构成通路,如果不是则没有
- 生成的最小生成树的路径应该为 顶点数 - 1条
- 克鲁斯卡尔算法的核心就是先连接距离最短的两个顶点,并不停的判断是否构成回路,先将所有最短的不构成回路的连接后,再考虑较长的路径,以达到较优的结果
- 源码见下
源码及分析
package algorithm.algorithm.kruskal;
/**
* @author AIMX_INFO
* @version 1.0
*/
@SuppressWarnings("ALL")
public class Kruskal {
//边的个数
private int edgeNum;
//顶点的值
private char[] vertex;
//邻接矩阵
private int[][] matrix;
//定义一个最大数表示未连通
private static final int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
//顶点
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
//邻接矩阵
int[][] matrix = {
{0, 12, INF, INF, INF, 16, 14},
{12, 0, 10, INF, INF, 7, INF},
{INF, 10, 0, 3, 5, 6, INF},
{INF, INF, 3, 0, 4, INF, INF},
{INF, INF, 5, 4, 0, 2, 8},
{16, 7, 6, INF, 2, 0, 9},
{14, INF, INF, INF, 8, 9, 0}
};
Kruskal kruskal = new Kruskal(vertex, matrix);
kruskal.kruskal();
}
//构造器
/**
* @param vertex 顶点个数
* @param matrix 邻接矩阵
*/
public Kruskal(char[] vertex, int[][] matrix) {
//初始化顶点数和边的个数
//根据实参确定顶点个数
int vLen = vertex.length;
//使用值传递的方式初始化顶点值
this.vertex = new char[vLen];
for (int i = 0; i < vLen; i++) {
this.vertex[i] = vertex[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 print() {
System.out.println("邻接矩阵:");
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
System.out.printf("%12d", matrix[i][j]);
}
System.out.println();
}
}
//使用冒泡排序根据边的权值对边进行排序
/**
* @param edges 边的集合
*/
public 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;
}
}
}
}
//根据顶点的值返回顶点在数组中的位置,即下标
public int getPosition(char v) {
for (int i = 0; i < vertex.length; i++) {
if (v == vertex[i]) {
return i;
}
}
return -1;
}
//获取途中的边,放到EData[]数组中
public EData[] getEdges() {
int index = 0;
EData[] edges = new EData[edgeNum];
for (int i = 0; i < vertex.length; i++) {
for (int j = i + 1; j < vertex.length; j++) {
if (matrix[i][j] != INF) {
edges[index++] = new EData(vertex[i], vertex[j], matrix[i][j]);
}
}
}
return edges;
}
//获取下标为 i 的顶点的终点
public int getEnd(int[] ends, int i) {
while (ends[i] != 0) {
i = ends[i];
}
return i;
}
//克鲁斯卡尔算法
public void kruskal() {
//index保存最后结果数组的索引
int index = 0;
//用于保存已有最小生成树的每个顶点在最小生成树中的终点
int[] ends = new int[edgeNum];
//创建结果数组,保存最终的结果
EData[] res = new EData[edgeNum];
//获取图中所有边的集合
EData[] edges = getEdges();
//对边按权值排序
sortEdge(edges);
//遍历edges数组,判断当前边是否和已有最小生成树构成回路,如果没有则加入
for (int i = 0; i < edges.length; i++) {
//获取第i条边的第一个顶点
int p1 = getPosition(edges[i].start);
//获取第i条边的第二个顶点
int p2 = getPosition(edges[i].end);
//获取两个顶点在最小生成树中的终点
int m = getEnd(ends, p1);
int n = getEnd(ends, p2);
//如果终点不同,说明没有构成回路
if (m != n) {
//先将起始点的终点指为终点
ends[m] = n;
//再将该边加入结果集
res[index++] = edges[i];
}
}
System.out.println("最小生成树:");
for (int i = 0; i < index; i++) {
if (res[i] != null) {
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 "[" +
start + "-" +
end +
"] --> " + weight
;
}
}