文章目录
一、Prim 算法
1.Prim 算法的核心原理:切分定理
切分:将一幅图分为两个不重叠且非空的节点集合
切分定理:
对于任意一种「切分」,其中权重最小的那条「横切边」一定是构成最小生成树的一条边。
使用切分定理,既然每一次「切分」一定可以找到最小生成树中的一条边,那我们就可以对图随意进行切分,每次都把权重最小的「横切边」拿出来加入最小生成树,直到把构成最小生成树的所有边都切出来为止。
2.切分流程
按照「切分」的定义,只要把图中的节点切成两个不重叠且非空的节点集合即可算作一个合法的「切分」,那么只切出来一个节点,也算是一个合法的「切分」
假设就从 A 点开始切分:
将节点A的所有横切边表示为 cut({A})
既然这是一个合法的「切分」,那么按照切分定理,这些「横切边」AB, AF 中权重最小的边一定是最小生成树中的一条边:
现在已经找到最小生成树的第一条边(边 AB),接下来围绕 A 和 B 这两个节点做切分:
然后又可以从这个切分产生的横切边(图中蓝色的边)中找出权重最小的一条边,也就又找到了最小生成树中的第二条边 BC:
接下来再围绕着 A, B, C 这三个点做切分,产生的横切边中权重最小的边是 BD,那么 BD 就是最小生成树的第三条边:
直到找到最小生成树的所有边为止。
3.Prim算法实现
切分具有以下性质:
即节点 A, B, C 的所有「横切边」(cut({A, B, C}))为节点A, B 的所有横切边与节点C的横切边(节点C的所有相邻边)之和。也就是说,在进行切分的过程中,我们只要不断把新节点的邻边加入横切边集合,就可以得到新的切分的所有横切边,同时用一个布尔数组 inMST 辅助,防止重复计算横切边就行了
代码实现:
class Prim {
// 核心数据结构,存储「横切边」的优先级队列
private PriorityQueue<int[]> pq;
// 类似 visited 数组的作用,记录哪些节点已经成为最小生成树的一部分
private boolean[] inMST;
// 记录最小生成树的权重和
private int weightSum = 0;
// graph 是用邻接表表示的一幅图,
// graph[s] 记录节点 s 所有相邻的边,
// 三元组 int[]{from, to, weight} 表示一条边
private List<int[]>[] graph;
public Prim(List<int[]>[] graph) {
this.graph = graph;
this.pq = new PriorityQueue<>((a, b) -> {
// 按照边的权重从小到大排序
return a[2] - b[2];
});
// 图中有 n 个节点
int n = graph.length;
this.inMST = new boolean[n];
// 随便从一个点开始切分都可以,我们不妨从节点 0 开始
inMST[