前言
编程语言:Java
, C++,Python,Js的朋友可以借助Chat GPT转换。
IDE: eclipse
, /写算法不用IDEA
前面说了最小生成树的Kruskal算法, 本篇简单说一下Prim算法, 它同样是一种从图中构造最小生成树的算法。
本篇将对比Kruskal算法详解prim算法。
Prim-wiki详细:可以完全参考这篇, 只需要看我的代码即可
关于代码实现, 需要建图!, 提供邻接表和链式前向星。 有关邻接矩阵的实现可以借助chatgpt转换。还是以洛谷这道模板。
Prim算法
Prim 与 Kruskal对比:引入
prim算法的使条件还是连通无向带权图。
Prim算法不如Kruskal算法常用。
Kruskal算法
是通过遍历图中所有边的方式, 优先选择最小权值最小的边。 K算法中点唯一的作用是判断是否成环。 判断成环的方式通过并查集, 如果两个点是位于同一集合,那么就舍弃当前边。
算法思想
Prim算法
中点很重要, 它是通过点的角度出发选择对应的边的, 直到选择的所有边涵盖了图中的所有顶点
Prim算法不必纠结证明, 因为它还是一种贪心策略, 局部最优 == 全局最优的结果。
需要借助两种数据结构: 集合, 小根堆(最小优先队列)
1. 初始化set集合和小根堆heap, 它们均为空容器。
2. 任意或者根据题意指定一个顶点作为开始点, 将点加入set里, 遍历该点的所有边加入到heap。
3. 从heap里弹出权值最小的边e, 通过边e去往另一个端点v。 此时, 有两种情况, 如果顶点v已经进入了set里, 那么说明边e不能添加进去,否则会成环; 如果顶点v不在set里, 那么边e可以添加到最小生成树, 同时端点v也要添加进set。
3. 重复步骤3, 如果当前图中所有顶点都被处理过, 那么heap迟早会成空堆, 因此空堆作为循环的终止条件。
以下摘自wiki
解释下图, 下图中选择D点作为初始点。
然后用颜色标记该点的所有边, 选择权值最小且符合条件不会成环的边。
下面它选择了D->A
这条边, 同时A,D应该进入集合,下次出现这两个点的边应该舍去。
A点附近的所有边解放, 再和原先的D附近边选出最小的边。显然D->F
满足条件, F点放进集合里, F点附近的边解放了。
现在可选的边{A->B:7,D->B:9,D->E:15,F->E:8,F->G:11}
, 已经加入最小生成树的点{A, D, F}
优先选择权值最小的边A->B
, B点进入集合。将B附近的边解放了, B->C:8, B->E:7
…如此,只要点处理完,最小生成树也就出来了。
注意, 上面描述省略了,将边重复加入的过程,比如D->B
的先进入,但处理B点时, 会再次将B->D
这组无向边重复添加进去, 虽然实际有set集合避免了重复处理, 但实际这个可以完全优化掉的。
后续部分请参考前言给出的wiki链接/
Java:邻接表实现
提交时需要将类名修改为Main
, 删除包名
提交链接跟Kruskal算法的洛谷题一致: 最小生成树
package MST;
import java.io.*;
import java.util.ArrayList;
import java.util.PriorityQueue;
public class Code02_Prim {
//Java中高效处理IO的写法
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while(in.nextToken() != StreamTokenizer.TT_EOF){
//n:图中顶点数
int n = (int)in.nval;
//邻接表方式进行建图。 int[]: * 2的数组, 0下标是边的下一个端点, 1下标是边的权值
ArrayList<ArrayList<int[]>> graph = new ArrayList<>();
//邻接表进行初始化
for(int i=0;i<=n;i++){
graph.add(new ArrayList<>());
}
in.nextToken();
//m:边的数量
int m = (int)in.nval;
for(int i=0,u, v, w;i < m;i++){
//读入起点,终点,权值 建立无向边。