最近刷题刷到要用到最小生成树的题,索性就对Prim算法和Kruskal算法做一个总结。
图的样例如下所示:
Prim算法:
算法思路:
1.初始条件:V={1,2,3,4,5,6},E为一个邻接矩阵。
2.首先创建两个集合vNew={},vOld=V。
3.接下来重复下列a,b两个操作,直到vNew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合vNew中的元素,v为集合vOld中的元素。
b.将v加入到集合vNew中,将vOld中的v去除。
4.打印。
代码实现:
public class Prim {
public static void main(String[] args) {
float m = Float.MAX_VALUE;
float[][] weight = {
{0, 0, 0, 0, 0, 0, 0},
{0, m, 6, 1, 5, m, m},
{0, 6, m, 5, m, 3, m},
{0, 1, 5, m, 5, 6, 4},
{0, 5, m, 5, m, m, 2},
{0, m, 3, 6, m, m, 6},
{0, m, m, 4, 2, 6, m}};
prim(weight);
}
public static void prim(float[][] weight){
int vNum = weight.length - 1;
List<Integer> newList = new ArrayList<>(); //新集合存放已经找到的点
List<Integer> oldList = new ArrayList<>(); //旧集合存放还未找到的点
for(int i=1;i<=6;i++){
oldList.add(i);
}
newList.add(1);
oldList.remove(new Integer(1));
while (newList.size() != vNum) {
float min = Float.MAX_VALUE;
int oldIndex = 1; //找到最小边时旧集合的点
int newIndex = 1; //找到最小边时新集合的点
for (int i = 0; i < newList.size(); i++) { //寻找新集合到旧集合的最小权值的边
int index = newList.get(i);
for (int j = 0; j <oldList.size(); j++) {
int k = oldList.get(j);
if (min > weight[index][k]) { //记录最小边的相关信息,用于打印
min = weight[index][k];
oldIndex = k;
newIndex = index;
}
}
}
System.out.println(newIndex + "->" + oldIndex + ": " + min);
weight[newIndex][oldIndex] = Float.MAX_VALUE; //找过的最小边置为最大值
weight[oldIndex][newIndex] = Float.MAX_VALUE; //对称边也要置为最大值
newList.add(oldIndex);
oldList.remove(new Integer(oldIndex));
}
}
}
注意: 这里有个坑,一开始我在代码最后写的是oldList.remove(oldIndex),但是发现删除的是oldIndex索引对应的值,而不是oldIndex对象。所以要改为oldList.remove(new Integer(oldIndex))。
打印结果如下图所示:
Kruskal算法
算法思路:
按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
Kruskal算法的思想很简单,但是实现起来如何保证不构成回路是一个难点,但是我在网上找到一个很巧妙的方法,代码实现如下:
public class Kruskal {
public static void main(String[] args) {
EdgeT[] edges = new EdgeT[10];
edges[0] = new EdgeT(1, 2, 6);
edges[1] = new EdgeT(1, 3, 1);
edges[2] = new EdgeT(1, 4, 5);
edges[3] = new EdgeT(2, 3, 5);
edges[4] = new EdgeT(2, 5, 3);
edges[5] = new EdgeT(3, 4, 5);
edges[6] = new EdgeT(3, 5, 6);
edges[7] = new EdgeT(3, 6, 4);
edges[8] = new EdgeT(4, 6, 2);
edges[9] = new EdgeT(5, 6, 6);
kruskal(edges);
}
public static void kruskal(EdgeT[] edges) {
Arrays.sort(edges, (o1,o2)->o1.weight-o2.weight);
// 定义一个一维数组,下标为边的起点,值为边的终点
int[] temp = new int[edges.length+1];
for (EdgeT edge : edges) {
// 找到起点和终点在临时边数组中的最后连接点
int start = find(temp, edge.start);
int end = find(temp, edge.end);
// 通过起点和终点找到的最后连接点是否为同一个点,是则产生回环
if (start != end) {
// 没有产生回环则将临时数组中,起点为下标,终点为值
temp[start] = end;
System.out.println(edge.start + "->" + edge.end + ":" + edge.weight);
}
}
}
public static int find(int temp[], int index) {
while (temp[index] > 0) {
index = temp[index];
}
return index;
}
}
class EdgeT {
int start;
int end;
int weight;
public EdgeT(int start, int end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
}
打印结果如下图所示: