数据结构 最小生成树 Prim算法
Prim算法(普里姆算法)
求加权连通图的最小生成树的算法,从一个点开始不断让树长大的过程, 并且保持权值最小:
从任意一个顶点开始,每次选择一个与当前顶点集最近的一个顶点,并将两顶点之间的边加入到树中。Prim算法在找当前最近顶点时使用到了贪婪算法
基本思想
1、对于图G而言,V是所有顶点的集合,设置两个新的集合U和T,
U存放G的最小生成树中的顶点,
T存放G的最小生成树中的边。
2、从所有uЄU,vЄ(V-U) (V-U表示出去U的所有顶点)的边中选取权值最小的边(u, v),将顶点v加入集合U中,将边(u, v)加入集合T中,如此不断重复,直到U=V为止,最小生成树构造完毕,这时集合T中包含了最小生成树中的所有边
算法描述:
1. 在一个加权连通图中,顶点集合V,边集合为E
2. 任意选出一个点作为初始顶点,标记为visit,计算所有与之相连接的点的距离,选择距离最短的,标记visit.
3. 重复以下操作,直到所有点都被标记为visit:
在剩下的点中,计算与已标记visit点距离最小的点(计算的是未标记点距离所有标记的点的最近距离),标记visit,证明加入了最小生成树
性能分析
具体性能也是取决于优先队列的选择,一般来说可以达到 O(ElogV)
时间复杂度
邻接矩阵:O(v^2)
邻接表:O(elog2v)
import java.io.IOException;
import java.util.Scanner;
public class Prim {
private char[] mVexs; // 顶点集合
private int[][] mMatrix; // 邻接矩阵
private static final int INF = 100;//Integer.MAX_VALUE; // 最大值
//创建图(用已提供的矩阵)
public Prim(char[] vexs, int[][] matrix) {// vexs-- 顶点数组 ,matrix-- 矩阵(数据)
// 初始化"顶点数"和"边数"
int vlen = vexs.length;
// 初始化"顶点"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];
// 初始化"边"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < vlen; i++)
for (int j = 0; j < vlen; j++)
mMatrix[i][j] = matrix[i][j];
}
//返回ch位置
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i]==ch)
return i;
return -1;
}
//返回顶点v的第一个邻接顶点的索引,失败则返回-1
private int firstVertex(int v) {
if (v<0 || v>(mVexs.length-1))
return -1;
for (int i = 0; i < mVexs.length; i++)
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
return i;
return -1;
}
//打印邻接顶点的索引、权重
private void nextVertexs() {
for(int v = 0; v < mVexs.length; v++){
System.out.print("\n结点"+v);
for (int i = 0; i < mVexs.length; i++) {
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
System.out.print("邻接点"+i+"(权重"+mMatrix[v][i]+")");
}
}
}
//打印矩阵队列图
public void print() {
int i, j, sum;
System.out.printf("结点表:");
for (i=0; i < mVexs.length; i++) {
sum = 0;
for (j=0; j < mVexs.length; j++) {
if (mMatrix[i][j]!=0 && mMatrix[i][j]!=INF) {
sum++;
}
}
System.out.print("\n结点"+i+", 标识"+mVexs[i]+", 共有邻接点"+sum);
}
System.out.print("\n\n");
System.out.printf("邻接矩阵:\n");
for (i = 0; i < mVexs.length; i++) {
for (j = 0; j < mVexs.length; j++)
System.out.printf("%3d ", mMatrix[i][j]);
System.out.printf("\n");
}
nextVertexs();
}
/*
* prim最小生成树: 邻接矩阵
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值
prims[index++] = mVexs[start]; // prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
// 初始化"顶点的权值数组",将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
weights[start] = 0; // 将第start个顶点的权值初始化为0, 可以理解为"第start个顶点到它自身的距离为0"。
for (int i = 0; i < num; i++) {
if(start == i) // 由于从start开始的,因此不需要再对第start个顶点进行处理。
continue;
int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(即已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}
// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点,将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}
// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
int n = getPosition(prims[i]); // 获取prims[i]在mMatrix中的位置
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min) // 在vexs[0...i]中,找出到j的权值最小的顶点。
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("\nPRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c \n", prims[i]);
System.out.printf("\n");
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ { 0, 12, INF, INF, INF, 16, 14},
/*B*/ { 12, 0, 10, INF, INF, 7, INF},
/*C*/ { INF, 10, 0, 3, 5, 6, INF},
/*D*/ { INF, INF, 3, 0, 4, INF, INF},
/*E*/ { INF, INF, 5, 4, 0, 2, 8},
/*F*/ { 16, 7, 6, INF, 2, 0, 9},
/*G*/ { 14, INF, INF, INF, 8, 9, 0}};
Prim pG;
pG = new Prim(vexs, matrix);
pG.print(); // 输出图的有关信息
pG.prim(0); // prim算法生成最小生成树
}
}
程序输出:
结点表:
结点0, 标识A, 共有邻接点3
结点1, 标识B, 共有邻接点3
结点2, 标识C, 共有邻接点4
结点3, 标识D, 共有邻接点2
结点4, 标识E, 共有邻接点4
结点5, 标识F, 共有邻接点5
结点6, 标识G, 共有邻接点3
邻接矩阵:
0 12 100 100 100 16 14
12 0 10 100 100 7 100
100 10 0 3 5 6 100
100 100 3 0 4 100 100
100 100 5 4 0 2 8
16 7 6 100 2 0 9
14 100 100 100 8 9 0
结点0邻接点1(权重12)邻接点5(权重16)邻接点6(权重14)
结点1邻接点0(权重12)邻接点2(权重10)邻接点5(权重7)
结点2邻接点1(权重10)邻接点3(权重3)邻接点4(权重5)邻接点5(权重6)
结点3邻接点2(权重3)邻接点4(权重4)
结点4邻接点2(权重5)邻接点3(权重4)邻接点5(权重2)邻接点6(权重8)
结点5邻接点0(权重16)邻接点1(权重7)邻接点2(权重6)邻接点4(权重2)邻接点6(权重9)
结点6邻接点0(权重14)邻接点4(权重8)邻接点5(权重9)
PRIM(A)=36: A
B
F
E
D
C
G