算法背景:
图中顶点A,B,C,D,E,F,G七个顶点表示七个村庄,现要修路,使得任意两个村庄之间都有通路,即都能到达(可以不是直达),问怎样修路可以使修路的总里程最短?
思路:
将所有边都连接起来,肯定都有通路,但总里程数一定不是最小。应该使用尽量少的路线,并使每条路线的长度最小,便可保证总里程最小。
最小生成树:
修路问题的本质就是最小生成树问题,最小生成树:Minimum Cost Spanning Tree,简称MST。具体为:给定一个带权的无向图,如何选取生成树,使得所有边上权的总和最小。
最小生成树特征:
- 有N个顶点,则N-1条边。
- 包含了图的全部顶点。
- N-1条边都在图中。
克鲁斯卡尔算法求最小生成树:
算法思路:
不同于普里姆算法的从点着手,克鲁斯卡尔算法以边为着手点,在所有的边的权值从小到大排序后,依次选边,使得在不构成回路的情况下形成最小生成树。
重点是对能否构成回路的判断:用一个end数组记录各顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"(单个顶点的终点是自身)。然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。
判断回路核心代码:
//求某一顶点的终点,若end数组为空,则终点是自身,否则终点就是在end数组中不断往后直到为0时的点。
//例如:end{0,3,4,4,0},B顶点与D顶点相连,D顶点又与E顶点相连,所以B的终点就是E顶点
//而C顶点的终点也是E顶点,所以当B与C相连时,就会构成回路。
private int getEnd(int []end,int i) {
while(end[i] != 0) {
i = end[i];
}
return i;
}
kruskal核心代码:
算法分析:
克鲁斯卡尔算法的外层循环是对图的所有排好序的边进行遍历,可以对内层在求索引时进行优化,即边的类中直接保存两端点的索引,不保存两端点的“名”,但由于输出时需要输出顶点名,所以可将边类设置成内部类,便可利用索引在图类的属性vertex找出顶点名。
算法的内层主要是找顶点的终点,即克鲁斯卡尔算法只取决于边,与顶点无关。适合于图中的顶点很多,但连接的边不多的情况,即适用于稀疏图。
public void kruskal() {
int []end = new int[vertex.size()];//保存终点的数组
Edge []result = new Edge[vertex.size() - 1];//保存最小生成树
Edge[] edgeAll = getEdgeAndSort();//得到图中排好序的边
int j = 0;
int sum = 0;//累积里程
for(int i = 0; i < edgeAll.length;i++) {//遍历排好序的边
int start = getIndex(edgeAll[i].start);//边的一个顶点索引
int last = getIndex(edgeAll[i].end);//边的另一个顶点索引
int m = getEnd(end, start);//求该索引动态连接的终点
int n = getEnd(end, last);
if(m != n) {
end[m] = n;//在向生成树中加入顶点时更新开始一端的终点
result[j++] = edgeAll[i];
sum += edgeAll[i].weight;
}
}
System.out.println();
for(Edge data : result) {//输出生成树
System.out.println(data);
}
System.out.println("需要修的总里程是" + sum + "米。");
}
java代码:
import java.util.*;
public class Kruskal {
public static void main(String[] args) {
final int INF = Integer.MAX_VALUE;
String []vertex = {"A","B","C","D","E","F","G"};
int [][]edge = new int[][] {
{INF,12,INF,INF,INF,16,14},
{12,INF,10,INF,INF,7,INF},
{7,10,INF,3,5,6,INF},
{INF,INF,3,INF,4,INF,INF},
{INF,INF,5,4,INF,2,8},
{16,7,6,INF,2,INF,9},
{14,INF,INF,INF,8,9,INF}
};
Graph graph = new Graph(vertex, edge);
graph.printMatrix();
graph.kruskal();
}
}
class Graph{
static final int INF = Integer.MAX_VALUE;
ArrayList<String> vertex;
int[][]edge;
int numOfEdge;
public Graph(String vertex[], int[][] edge) {
int len = vertex.length;
this.vertex = new ArrayList<String>(len);
this.edge = new int [len][len];
for(int i = 0;i < len;i++) {
this.vertex.add(vertex[i]);
for(int j = 0;j < len;j++) {
this.edge[i][j] = edge[i][j];
}
}
for(int i = 0; i < len;i++) {
for(int j = i + 1;j < len;j++) {
if(edge[i][j] != INF) {
numOfEdge++;
}
}
}
}
public void printMatrix() {
System.out.println("邻接矩阵表示为:");
for(int[] data : edge) {
System.out.println(Arrays.toString(data));
}
}
public Edge[] getEdgeAndSort() {
int index = 0;
Edge[] allEdge = new Edge[numOfEdge];
for(int i = 0; i < vertex.size();i++) {
for(int j = i + 1; j < vertex.size();j++) {
if(edge[i][j] != INF) {
allEdge[index++] = new Edge(vertex.get(i), vertex.get(j), edge[i][j]);
}
}
}
Arrays.sort(allEdge);
return allEdge;
}
private int getIndex(String s) {
for(int i = 0; i < vertex.size();i++) {
if(vertex.get(i).equals(s)) {
return i;
}
}
return -1;
}
private int getEnd(int []end,int i) {
while(end[i] != 0) {
i = end[i];
}
return i;
}
public void kruskal() {
int []end = new int[vertex.size()];
Edge []result = new Edge[vertex.size() - 1];
Edge[] edgeAll = getEdgeAndSort();
int j = 0;
int sum = 0;
for(int i = 0; i < edgeAll.length;i++) {
int start = getIndex(edgeAll[i].start);
int last = getIndex(edgeAll[i].end);
int m = getEnd(end, start);
int n = getEnd(end, last);
if(m != n) {
end[m] = n;
result[j++] = edgeAll[i];
sum += edgeAll[i].weight;
}
}
System.out.println();
for(Edge data : result) {
System.out.println(data);
}
System.out.println("需要修的总里程是" + sum + "米。");
}
}
class Edge implements Comparable<Edge>{
String start;
String end;
int weight;
public Edge(String start, String end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "应连接:(" + start + "," + end + "),权值(距离)为:" + weight;
}
@Override
public int compareTo(Edge o) {
return this.weight - o.weight;
}
}
程序输出:
邻接矩阵表示为:
[2147483647, 12, 2147483647, 2147483647, 2147483647, 16, 14]
[12, 2147483647, 10, 2147483647, 2147483647, 7, 2147483647]
[7, 10, 2147483647, 3, 5, 6, 2147483647]
[2147483647, 2147483647, 3, 2147483647, 4, 2147483647, 2147483647]
[2147483647, 2147483647, 5, 4, 2147483647, 2, 8]
[16, 7, 6, 2147483647, 2, 2147483647, 9]
[14, 2147483647, 2147483647, 2147483647, 8, 9, 2147483647]
应连接:(E,F),权值(距离)为:2
应连接:(C,D),权值(距离)为:3
应连接:(D,E),权值(距离)为:4
应连接:(B,F),权值(距离)为:7
应连接:(E,G),权值(距离)为:8
应连接:(A,B),权值(距离)为:12
需要修的总里程是36米。
优化后:(边类设置为内部类)
import java.util.*;
public class Kruskal {
public static void main(String[] args) {
final int INF = Integer.MAX_VALUE;
String []vertex = {"A","B","C","D","E","F","G"};
int [][]edge = new int[][] {
{INF,12,INF,INF,INF,16,14},
{12,INF,10,INF,INF,7,INF},
{7,10,INF,3,5,6,INF},
{INF,INF,3,INF,4,INF,INF},
{INF,INF,5,4,INF,2,8},
{16,7,6,INF,2,INF,9},
{14,INF,INF,INF,8,9,INF}
};
Graph graph = new Graph(vertex, edge);
graph.printMatrix();
graph.kruskal();
}
}
class Graph{
static final int INF = Integer.MAX_VALUE;
ArrayList<String> vertex;
int[][]edge;
int numOfEdge;
class Edge implements Comparable<Edge>{
int start;
int end;
int weight;
public Edge(int start, int end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "应连接:(" + vertex.get(start) + "," + vertex.get(end) + "),权值(距离)为:" + weight;
}
@Override
public int compareTo(Edge o) {
return this.weight - o.weight;
}
}
public Graph(String vertex[], int[][] edge) {
int len = vertex.length;
this.vertex = new ArrayList<String>(len);
this.edge = new int [len][len];
for(int i = 0;i < len;i++) {
this.vertex.add(vertex[i]);
for(int j = 0;j < len;j++) {
this.edge[i][j] = edge[i][j];
}
}
for(int i = 0; i < len;i++) {
for(int j = i + 1;j < len;j++) {
if(edge[i][j] != INF) {
numOfEdge++;
}
}
}
}
public void printMatrix() {
System.out.println("邻接矩阵表示为:");
for(int[] data : edge) {
System.out.println(Arrays.toString(data));
}
}
public Edge[] getEdgeAndSort() {
int index = 0;
Edge[] allEdge = new Edge[numOfEdge];
for(int i = 0; i < vertex.size();i++) {
for(int j = i + 1; j < vertex.size();j++) {
if(edge[i][j] != INF) {
allEdge[index++] = new Edge(i, j, edge[i][j]);
}
}
}
Arrays.sort(allEdge);
return allEdge;
}
private int getEnd(int []end,int i) {
while(end[i] != 0) {
i = end[i];
}
return i;
}
public void kruskal() {
int []end = new int[vertex.size()];
Edge []result = new Edge[vertex.size() - 1];
Edge[] edgeAll = getEdgeAndSort();
int j = 0;
int sum = 0;
for(int i = 0; i < edgeAll.length;i++) {
int start = edgeAll[i].start;
int last = edgeAll[i].end;
int m = getEnd(end, start);
int n = getEnd(end, last);
if(m != n) {
end[m] = n;
result[j++] = edgeAll[i];
sum += edgeAll[i].weight;
}
}
System.out.println();
for(Edge data : result) {
System.out.println(data);
}
System.out.println("需要修的总里程是" + sum + "米。");
}
}