1.概念
Prim算法是一种计算加权无向图的最小生成树的算法。所谓最小生成树,是指一个图的子图,它包含图中所有的顶点,并且有保持图连通的最少的边,且所有边的权值之和最小。
Prim算法的基本思想是从图中任意一个顶点开始,逐渐增长最小生成树。在每一步,算法都会选择连接已选顶点和未选顶点之间权重最小的边,并将其加入到最小生成树中,同时将新加入的顶点标记为已选。这个过程一直重复,直到所有的顶点都被选中,最终形成最小生成树。
Prim算法的具体步骤如下:
1. 初始化:选择一个起始顶点v,加入到最小生成树中,并将与v相连的边和它们的权重记录下来。
2. 循环执行以下步骤,直到所有顶点都被加入最小生成树:
a. 在所有连接已选顶点和未选顶点的边中,找到权重最小的边(u, v),其中u是已选顶点,v是未选顶点。
b. 将边(u, v)加入到最小生成树中,并将顶点v标记为已选。
c. 更新与顶点v相连的边和它们的权重的记录。
3. 当所有顶点都被加入最小生成树后,算法结束,最小生成树形成。
Prim算法的时间复杂度取决于所使用的的数据结构。如果使用邻接矩阵表示图,算法的时间复杂度为O(V^2),其中V是顶点的数量。如果使用优先队列(例如斐波那契堆)来实现,算法的时间复杂度可以降低到O(E + VlogV),其中E是边的数量。
1.最初T的顶点集合TV={A},V-TV{B,C,D,E,F},边的集合为TE={}。
2.在所有的邻接边中,选择权值最小的边,如(A,B),(A,C),(A,D),中最小的权值边为(A,D)。不断重复这个过程,将边(u, v)加入到最小生成树中,并将顶点v标记为已选。 更新与顶点v相连的边和它们的权重的记录。
2.Prim的算法实现
设已构造一个有n个顶点的图G,Prim算法使用一个数组mst记录G的一颗最小生成树,我们可以使用优先队列(通常是一个最小堆)来高效地选择最小边。
下面是实现Prim算法的代码
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
public class prim {
private char[] mVexs;//顶点集合
private int[][] mMatrix;//邻接矩阵
private static final int INF =Integer.MAX_VALUE;
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];//顶点间边的权值
//prim最小生成树中第一个数是“图中第start个顶点”,因为是从start开始的
prims[index++]=mVexs[start];
//初始化“顶点的权值数组”
//将每个顶点的权值初始化为“第start个顶点”到“该顶点”的权值
for (int i =0;i<num;i++){
weights[i]=mMatrix[start][i];
//将第start个顶点的权值初始化为0
//可以理解为“第start个顶点到它自身的距离为0”
weights[start]=0;
for(int j =0;j<num;j++){
//由于从start开始的,因此不需要再对第start个顶点进行处理
if(start==j)
continue;
int i1 =0;
int k =0;
int min=INF;
//再未加入到最小生成树的顶点中,找出权值最小的顶点
while(i1<num){
//若weight[j]=0,意味着“第i1个节点已经被排序过”
if(weights[i1]!=0&&weights[i1]<min){
min=weights[j];
k=i1;
}
i++;
}
//在经过上面的处理之后,在未加入到最小生成树的顶点中,权值最小的顶点时第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];
}
}
}
}
}
}
单源最短路径
单源最短路径是指从一个顶点vi到图中其他顶点的最短路径。Dijkstra针对非负权值的带权图,提出一个按路径长度递增次序逐步求得单源最短路径的算法。通常称为Dijkstra算法。
1.Dijkstra算法描述
Dijkstra算法思想是:逐步求解,每步将一条最短路径扩充一条边形成下一条最短路径,并将其他路径替换成更短的。
Dijktra算法描述:
1、初始化一个距离数组dist[ ],其中dist[i]表示从源点到定点i的当前已知最短距离。初始时,源点到自身的距离设置为0,其余定点的距离设置为无穷大。同时设置一个标记数组path来记录哪些定点的最短路径已经被确定。
2、确定最小距离顶点:从未确定最短路径的顶点集合中,选择一个具有最小dist[]值的顶点u,将其加入到已经确定最短路径的集合s中
3、更新距离:对于顶点u的每一个邻接顶点v如果通过顶点u到达个路径小于当前已知的dist[v],则更新dist[v]为
如图所示v1-v6六个点以及他们的有向权重连线,现在我们假设从v1出发,画出从顶点v1到其余各点最短路径的过程。
该算法需要使用3个数组的定义
- s数组用于存储前述集合s
- dist数组用于保存最短路径长度
- path数组保存最短路径经过顶点序列
- v用于存储还有多少个数组没有被确认
数组s | 数组v | dist[] | path |
{v1} | {v2,v3,v4,v5,v6} | {0,10,12,oo,oo,oo} | {v1,v1,v1,-1,-1,-1} |
{v1,v2} |
- 首先,我们将v1拿出来,v1能连接v2和v3,v1到v1的距离我们可以看成0,v1到v2的距离是10,v1到v3的距离是12。
- v1不能直接到达v4,v5,v6,我们可以看成无穷大,那么v1的上一个节点是v1,但是v4,v5,v6目前还没有连接到节点就记录为-1.
- 然后我们根据数组{0,10,12,oo,oo,oo},找到数组的最小值,即v2的10,我们将v2加入到数组s中
数组s | 数组v | dist[] | path |
{v1} | {v2,v3,v4,v5,v6} | {0,10,12,oo,oo,oo} | {v1,v1,v1,-1,-1,-1} |
{v1,v2} | {v3,v4,v5,v6} | {0,10,12,26,35,oo} | {v1,v1,v1,v2,v2,-1} |
{v1,v2,v3} |
- 我们从距离数组中{0,10,12,26,35,oo},选取最小值,即v3节点加入数组s中,数组v为{v4,v5,v6}。
- 现在有另一个路线为v1-v3-v2,但是长度为15比10大,所以不做变化,v1到v3的距离还是12,所以v3的上一个节点还是v1。
- v4和v5是可以根据v2进行跟进的,v4 =10+16=26,v5 =10 +15 =35。
数组s | 数组v | dist[] | path |
{v1} | {v2,v3,v4,v5,v6} | {0,10,12,oo,oo,oo} | {v1,v1,v1,-1,-1,-1} |
{v1,v2} | {v3,v4,v5,v6} | {0,10,12,26,35,oo} | {v1,v1,v1,v2,v2,-1} |
{v1,v2,v3} | {v4,v5,v6} | {0,10,12,24,35,20} | {v1,v1,v1,v3,v2,v3} |
{v1,v2,v3,v6} | {v4,v5} |
- 我们在数组中{0,10,12,24,35,20}可以看出在去掉v1,v2,v3之后的最小点是v6,我们将v6加入到数组当中, 所以我们将v6加入到数组s中,v1到v1,v2,v3的距离保持不变。
- v1到v4的路径,多了个v6,所以多出来了个路径v1-v3-v6-v4,距离是22,比之前的24小,进行更新,所以v4的上一个节点变成了v6。
- 然后v1到v5,多增加了路线了,v1-v3-v6-v5,距离变成了30,比之前的35要小,更新表格,v5的上一个节点变成v6。
数组s | 数组v | dist[] | path |
{v1} | {v2,v3,v4,v5,v6} | {0,10,12,oo,oo,oo} | {v1,v1,v1,-1,-1,-1} |
{v1,v2} | {v3,v4,v5,v6} | {0,10,12,26,35,oo} | {v1,v1,v1,v2,v2,-1} |
{v1,v2,v3} | {v4,v5,v6} | {0,10,12,24,35,20} | {v1,v1,v1,v3,v2,v3} |
{v1,v2,v3,v6} | {v4,v5} | {0,10,12,22,30,20} | {v1,v1,v1,v6,v6,v3} |
{v1,v2,v3,v6,v4} | {v5} |
从数组v中取出距离最短的值v4放入数组s中,此时v1到v1,v2,v3,v4的距离保持不变,v1-v5的距离多出一条v1-v3-v6-v4-v5,路径29比之前的30要短,更新表格,所以v5的上一个节点是v4。
数组s | 数组v | dist[] | path |
{v1} | {v2,v3,v4,v5,v6} | {0,10,12,oo,oo,oo} | {v1,v1,v1,-1,-1,-1} |
{v1,v2} | {v3,v4,v5,v6} | {0,10,12,26,35,oo} | {v1,v1,v1,v2,v2,-1} |
{v1,v2,v3} | {v4,v5,v6} | {0,10,12,24,35,20} | {v1,v1,v1,v3,v2,v3} |
{v1,v2,v3,v6} | {v4,v5} | {0,10,12,22,30,20} | {v1,v1,v1,v6,v6,v3} |
{v1,v2,v3,v6,v4} | {v5} | {0,10,12,22,30,20} | {v1,v1,v1,v6.v4.v3} |
{v1,v2,v3,v6,v4,v5} | {} | {0,10,12,22,30,20} | {v1,v1,v1,v6.v4.v3} |
public class Dijkstra {
private Queue visited;
int[] distance;
public Dijkstra(int len) {
// TODO Auto-generated constructor stub
visited = new LinkedList();
distance = new int[len];
}
private int getIndex(Queue q, int[] dis) {
int k = -1;
int min_num = Integer.MAX_VALUE;
for (int i = 0; i < dis.length; i++) {
if (!q.contains(i)) {
if (dis[i] < min_num) {
min_num = dis[i];
k = i;
}
}
}
return k;
}
public void dijkstra(int[][] weight, Object[] str, int v) {
HashMap path;
path = new HashMap();
for (int i = 0; i < str.length; i++)
path.put(i, "");
//初始化路径长度数组distance
for (int i = 0; i < str.length; i++) {
path.put(i, path.get(i) + "" + str[v]);
if (i == v)
distance[i] = 0;
else if (weight[v][i] != -1) {
distance[i] = weight[v][i];
path.put(i, path.get(i) + "-->" + str[i]);
} else
distance[i] = Integer.MAX_VALUE;
}
visited.add(v);
while (visited.size() < str.length) {
int k = getIndex(visited, distance);//获取未访问点中距离源点最近的点
visited.add(k);
if (k != -1) {
for (int j = 0; j < str.length; j++) {
//判断k点能够直接到达的点
if (weight[k][j] != -1) {
//通过遍历各点,比较是否有比当前更短的路径,有的话,则更新distance,并更新path。
if (distance[j] > distance[k] + weight[k][j]) {
distance[j] = distance[k] + weight[k][j];
path.put(j, path.get(k) + "-->" + str[j]);
}
}
}
}
}
for (int h = 0; h < str.length; h++) {
System.out.printf(str[v] + "-->" + str[h] + ":" + distance[h] + " ");
if (distance[h] == Integer.MAX_VALUE)
System.out.print(str[v] + "-->" + str[h] + "之间没有可通行路径");
else
System.out.print(str[v] + "-" + str[h] + "之间有最短路径,具体路径为:" + path.get(h).toString());
System.out.println();
}
visited.clear();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[][] weight = {
{0, 10, 12, -1, -1, -1},
{-1, 0, -1, 16, 25, -1},
{4, 3, 0, 12, -1, 8},
{-1, -1, -1, 0, 7, -1},
{-1, -1, -1, -1, 0, -1},
{-1, -1, -1, 2, -1, 0}};
String[] str = {"V1", "V2", "V3", "V4", "V5", "V6"};
int len = str.length;
Dijkstra dijkstra = new Dijkstra(len);
//依次让各点当源点,并调用dijkstra函数
for (int i = 0; i < str.length; i++) {
dijkstra.dijkstra(weight, str, i);
}
}
}
运行结果:
V1-->V1:0 V1-V1之间有最短路径,具体路径为:V1
V1-->V2:10 V1-V2之间有最短路径,具体路径为:V1-->V2
V1-->V3:12 V1-V3之间有最短路径,具体路径为:V1-->V3
V1-->V4:22 V1-V4之间有最短路径,具体路径为:V1-->V3-->V6-->V4
V1-->V5:29 V1-V5之间有最短路径,具体路径为:V1-->V3-->V6-->V4-->V5
V1-->V6:20 V1-V6之间有最短路径,具体路径为:V1-->V3-->V6
V2-->V1:2147483647 V2-->V1之间没有可通行路径
V2-->V2:0 V2-V2之间有最短路径,具体路径为:V2
V2-->V3:2147483647 V2-->V3之间没有可通行路径
V2-->V4:16 V2-V4之间有最短路径,具体路径为:V2-->V4
V2-->V5:23 V2-V5之间有最短路径,具体路径为:V2-->V4-->V5
V2-->V6:2147483647 V2-->V6之间没有可通行路径
V3-->V1:4 V3-V1之间有最短路径,具体路径为:V3-->V1
V3-->V2:3 V3-V2之间有最短路径,具体路径为:V3-->V2
V3-->V3:0 V3-V3之间有最短路径,具体路径为:V3
V3-->V4:10 V3-V4之间有最短路径,具体路径为:V3-->V6-->V4
V3-->V5:17 V3-V5之间有最短路径,具体路径为:V3-->V6-->V4-->V5
V3-->V6:8 V3-V6之间有最短路径,具体路径为:V3-->V6
V4-->V1:2147483647 V4-->V1之间没有可通行路径
V4-->V2:2147483647 V4-->V2之间没有可通行路径
V4-->V3:2147483647 V4-->V3之间没有可通行路径
V4-->V4:0 V4-V4之间有最短路径,具体路径为:V4
V4-->V5:7 V4-V5之间有最短路径,具体路径为:V4-->V5
V4-->V6:2147483647 V4-->V6之间没有可通行路径
V5-->V1:2147483647 V5-->V1之间没有可通行路径
V5-->V2:2147483647 V5-->V2之间没有可通行路径
V5-->V3:2147483647 V5-->V3之间没有可通行路径
V5-->V4:2147483647 V5-->V4之间没有可通行路径
V5-->V5:0 V5-V5之间有最短路径,具体路径为:V5
V5-->V6:2147483647 V5-->V6之间没有可通行路径
V6-->V1:2147483647 V6-->V1之间没有可通行路径
V6-->V2:2147483647 V6-->V2之间没有可通行路径
V6-->V3:2147483647 V6-->V3之间没有可通行路径
V6-->V4:2 V6-V4之间有最短路径,具体路径为:V6-->V4
V6-->V5:9 V6-V5之间有最短路径,具体路径为:V6-->V4-->V5
V6-->V6:0 V6-V6之间有最短路径,具体路径为:V6
Process finished with exit code 0