图的邻接矩阵表示法可参考:https://www.jianshu.com/p/9f27288f6749
测试图如图所示:
普里姆(Prim)算法
思想:先选取一个顶点加入最小生成树,再选取与该顶点相连的边中的最小权值对应的顶点加入生成树,将这两个顶点作为一棵新的最小生成树,继续判断与该树相连的边的最小权值对应的顶点,并将其加入最小生成树,直到所有顶点均加入生成树为止。
//最小生成树--Prim算法
//每次都是从lowcost数组中选择权值最小的边加入生成树
public void MiniSpanTree_Prim(MyGraph graph) {
int min, i, j, k;
//保存相关顶点下标
int[] adjvex = new int[graph.getNumOfVertex()];
//保存相关顶点间边的权值
int[] lowcost = new int[graph.getNumOfVertex()];
/**
* lowcost值为0时表示此下标的顶点已经加入最小生成树,也就是将v0将入生成树
* 因为该程序默认从0号下标顶点开始建立最小生成树,所以将lowcost初始化为0
* 但最小生成树的建立并不一定需要从0号下标顶点开始
**/
lowcost[0] = 0;
//初始化第一个顶点的下标为0
adjvex[0] = 0;
for(i = 1; i < graph.getNumOfVertex(); i++) {
//将与v0顶点有边的权值存入数组
lowcost[i] = edges[0][i];
//初始化都为v0的下标
adjvex[i] = 0;
}
for(i = 1; i < graph.getNumOfVertex(); i++) {
//初始化最小权值,一般设置为一个不可能的大数字,此处是65535
min = INF;
//j用来作为顶点下标的循环变量;k用来存储最小权值顶点的下标
j = 1; k = 0;
while(j < graph.getNumOfVertex()) {
//如果权值不为0且权值小于min
if (lowcost[j] != 0 && lowcost[j] < min) {
//让当前权值称为最小值
min = lowcost[j];
//将当前最小值的下标存入k
k = j;
}
j++;
}
//输出当前顶点边中权值最小的边
System.out.println("("+adjvex[k]+","+k+")");
//将当前顶点的权值设置为0,表示此顶点已经加入生成树
lowcost[k] = 0;
for(j =1; j < graph.getNumOfVertex(); j++) {
//若下标为k的顶点各边权值小于此前这些顶点未被加入生成树权值
if (lowcost[j] != 0 && edges[k][j] < lowcost[j]) {
//将较小权值存入lowcost
lowcost[j] = edges[k][j];
//将下标为k的顶点存入adjvex
adjvex[j] = k;
}
}
}
}
测试程序
int n = 9;
String[] vertex = {"v0","v1","v2","v3","v4","v5","v6","v7","v8"};
MyGraph graph = new MyGraph(n);
for (String string : vertex) {
graph.insertVertex(string);
}
graph.insertEdge(0, 1, 10);
graph.insertEdge(0, 5, 11);
graph.insertEdge(1, 2, 18);
graph.insertEdge(1, 6, 16);
graph.insertEdge(1, 8, 12);
graph.insertEdge(2, 8, 8);
graph.insertEdge(2, 3, 22);
graph.insertEdge(3, 8, 21);
graph.insertEdge(3, 6, 24);
graph.insertEdge(3, 4, 20);
graph.insertEdge(3, 7, 16);
graph.insertEdge(4, 5, 26);
graph.insertEdge(4, 7, 7);
graph.insertEdge(5, 6, 17);
graph.insertEdge(6, 7, 19);
graph.MiniSpanTree_Prim(graph);
测试结果:
克鲁斯卡尔算法(Kruskal)
思想:将图的存储结构使用边集数组的形式表示,并将边集数组按权值从小到大排序,遍历边集数组,每次选取一条边并判断是否构成环路,不会构成环路则将其加入最小生成树,最终只会包含n-1条边(n为无向图的顶点数)。
边集数组的结构如图所示:
边集数组类
public class Edge implements Comparable {
int begin;
int end;
int weight;
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
// TODO Auto-generated method stub
//按权值升序排列
return this.weight - o.weight;
}
}
图类
public class Graph {
private ArrayList
测试程序:
int n = 9, e = 15;
String[] vertex = {"v0","v1","v2","v3","v4","v5","v6","v7","v8"};
Graph graph = new Graph(n,e);
for (String string : vertex) {
graph.insertVertex(string);
}
graph.insertEdge(0, 1, 10);
graph.insertEdge(0, 5, 11);
graph.insertEdge(1, 2, 18);
graph.insertEdge(1, 6, 16);
graph.insertEdge(1, 8, 12);
graph.insertEdge(2, 8, 8);
graph.insertEdge(2, 3, 22);
graph.insertEdge(3, 8, 21);
graph.insertEdge(3, 6, 24);
graph.insertEdge(3, 4, 20);
graph.insertEdge(3, 7, 16);
graph.insertEdge(4, 5, 26);
graph.insertEdge(4, 7, 7);
graph.insertEdge(5, 6, 17);
graph.insertEdge(6, 7, 19);
graph.MiniSpanTree_Kruskal(graph);
测试结果:
最小生成树为:
总结
普里姆算法针对顶点展开,通过不断寻找与已构建的生成树的最小边来不断构建新的生成树。普里姆算法对于稠密图,也就是边数非常多的情况会更好一些,因为其是通过顶点来展开的。算法时间损耗主要来源于嵌套的for循环,所以时间复杂度为O(n^2)。
克鲁斯卡尔算法针对边展开,通过对边集数组的遍历来构建最小生成树,但是过程中必须避免构成环路。克鲁斯卡尔算法对于稀疏图,也就是边数较少的情况效率会很高。此算法的Find函数由边数e决定,时间复杂度为O(loge),再加上外层for循环的e次,所以时间复杂度为O(eloge)。