引言
现在假设有一个很实际的问题:我们要在n个城市中建立一个通信网络,则连通这n个城市需要布置n-1一条通信线路,这个时候我们需要考虑如何在成本最低的情况下建立这个通信网?
于是我们就可以引入连通图来解决我们遇到的问题,n个城市就是图上的n个顶点,然后,边表示两个城市的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。
最小生成树算法思路
构造最小生成树有很多算法,但是他们都是利用了最小生成树的同一种性质:MST性质:假设图N=(V,E)是一个连通网,U是顶点集V的一个非空子集,如果(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必定存在一颗包含边(u,v)的最小生成树,今天我们学习使用MST性质生成最小生成树的算法:普里姆算法。
普里姆算法的关键就是寻找集合U 和V-U之间的最小权边(u,v),然后把v顶点加入U,它把顶点集分割成两部分,每一次操作就从V-U寻找顶点v,把它放到集合U中,这样保证生成树没有回环,俗称"避圈法"。实现流程如下:
1.原始图:
2.假设我们从顶点v1开始,所以我们可以发现(v1,v3)边的权重最小,所以第一个输出的边就是:(v1,v3):
3.从v1和v3作为起点的边中寻找权重最小的边,发现(v3,v6)这条边最小,所以输出边就是(v3,v6):
4.从v1、v3、v6这三个点相关联的边中寻找一条权重最小的边,我们可以发现边(v6,v4)权重最小,所以输出边就是(v6,v4):
5.从v1、v3、v6、v4这四个顶点相关联的边中寻找权重最小的边,发现边(v3, v2)的权重最小,所以输出边:(v3, v2):
6.从v1、v3、v6、v4、v2这五个顶点相关联的边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以输出边(v2,v5):
7.六个顶点全部连接,最小生成树构建完成.
算法流程:
1>设u为起点,无向图的邻接矩阵C[N][N],U为已经加入生成树的顶点集合,V-U为未加入生成树的顶点集合,s[]数组做为顶点所属集合的标记。closest[]数组存放V-U中顶点j到集合U中距离最近的顶点索引,如上面的步骤2中,closest[3]=1,表示顶点V3的最近顶点为V1.lowcost[]数组存放V-U集合中到U最近顶点的边权值,如步骤2中,lowcost[3]=1,表示V3到集合{V1}的最小权重为1.
2>.初始化U={u},closest[]、lowcost[]和s[].
3>.在V-U中寻找lowcost[]最小的顶点t,加入集合U.
4>.更新lowcost和closest,更新公式:如果C[t][j]
代码实现
package graphic;
/**
* Created by chenming on 2018/6/18
* 最小生成树算法-普利姆算法
*/
public class Prim {
private int[][] map;
//V-U集合中距离U集合中顶点最近的点,如closest[j] = i,
// 表示j距离U集合最近的顶点为i,在最小树中,i为j的前驱
private int[] closest;
private int[] lowcost;//V-U集合中距离U最近顶点的边长(j, closest[j])
private boolean[] s;//标记是否添加到集合U
public Prim(int[][] map) {
this.map = map;
int size = map.length;
closest = new int[size];
lowcost = new int[size];
s = new boolean[size];
}
/**
* 构造最小生成树
*
* @param u0 起点顶点
*/
public void prim(int u0) {
int n = map.length;
//Step1初始化
s[u0] = true;//u0加入集合
for (int i = 0; i < n; i++) {
if (i != u0) {
lowcost[i] = map[u0][i];
closest[i] = u0;
} else {
lowcost[i] = 0;
closest[i] = -1;//-1表示没有前驱
}
}
//Step2 S-U中寻找距离U最小的顶点t,加入集合,更新lowcost和closest
for (int i = 0; i < n; i++) {
//寻找
int min = Integer.MAX_VALUE;
//找最小权边顶点t
int t = u0;
for (int j = 0; j < n; j++) {
if (!s[j] && lowcost[j] < min) {
t = j;
min = lowcost[j];
}
}
//加入集合
if (t == u0) {//没找到最小值,跳出循环
break;
}
s[t] = true;
//更新lowcost和closest,从最小顶点t出发,如果它的邻接顶点j的边权小于lowcost[j],则更新lowcost和closest
for (int j = 0; j < n; j++) {
if (!s[j] && map[t][j] < lowcost[j]) {
lowcost[j] = map[t][j];
closest[j] = t;
}
}
}
//输出结果
dumpResult();
}
/**
* 输出边和顶点即可
*/
private void dumpResult() {
System.out.println("======最小树结构======");
for (int i = 0; i < lowcost.length; i++) {
System.out.println("顶点" + i + "的前驱:" + closest[i]);
}
System.out.println("======最小树权值======");
for (int i = 0; i < lowcost.length; i++) {
System.out.println("权值:" + lowcost[i]);
}
}
}
测试代码:
@Test
public void testPrim() {
int INF = Integer.MAX_VALUE;
int[][] map = {
{INF, 23, INF, INF, INF, 28, 36},
{23, INF, 20, INF, INF, INF, 1},
{INF, 20, INF, 15, INF, INF, 4},
{INF, INF, 15, INF, 3, INF, 9},
{INF, INF, INF, 3, INF, 17, 16},
{28, INF, INF, INF, 17, INF, 25},
{36, 1, 4, 9, 16, 25, INF}
};
Prim prim = new Prim(map);
prim.prim(0);
}
测试结果:
======最小树结构======
顶点0的前驱:-1
顶点1的前驱:0
顶点2的前驱:6
顶点3的前驱:6
顶点4的前驱:3
顶点5的前驱:4
顶点6的前驱:1
======最小树权值======
权值:0
权值:23
权值:4
权值:9
权值:3
权值:17
权值:1
完整代码地址:数据结构与算法学习JAVA描述GayHub地址