关于 Prim 算法的核心思想 可见 图论 -- Prim 算法部分
对于算法的形象化模拟可以看下面动图
下面和 Kruskal 算法一样,解释以下三个问题,可与 Kruskal 算法对比观看
问题一:利用inMST[]
数组来判断所有节点是否已在生成树中,详细实现可见allConnected()
方法
问题二:利用inMST[]
数组来记录已加入生成树中的节点,以保证无环
问题三:利用优先队列按照边的权重从小到大排序,可保证最终的生成子树为最小生成子树
算法模版
xpublic class Prim {
// 存在横切边的数据结构
private final Queue<int[]> pq;
// 记录已经成为最小生成树的节点
private boolean[] inMST;
// 记录最小生成树的权重和
private Integer weightSum = 0;
// graph 是用邻接表表示的一幅图
// graph[s] 记录节点 s 所有相邻的边
// 三元组 int[]{from, to, weight} 表示一条边
private final List<int[]>[] graph;
public Prim(List<int[]>[] graph) {
this.graph = graph;
// 按照边的权重从小到大排序
this.pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
// 图中有 n 个节点
int n = graph.length;
this.inMST = new boolean[n];
// 随便从一个点开始切分都可以,我们不妨从节点 0 开始
inMST[0] = true;
cut(0);
// 不断进行切分,向最小生成树中添加边
while (!pq.isEmpty()) {
int[] edge = pq.poll();
int to = edge[1];
int weight = edge[2];
if (inMST[to]) {
// 节点 to 已经在最小生成树中,跳过
// 否则这条边会产生环
continue;
}
// 将边 edge 加入最小生成树
weightSum += weight;
inMST[to] = true;
// 节点 to 加入后,进行新一轮切分,会产生更多横切边
cut(to);
}
}
// 将 s 的横切边加入优先队列
private void cut(int x) {
// 遍历 s 的邻边
for (int[] edge : graph[x]) {
int to = edge[1];
if (inMST[to]) {
// 相邻接点 to 已经在最小生成树中,跳过
// 否则这条边会产生环
continue;
}
// 加入横切边队列
pq.offer(edge);
}
}
// 最小生成树的权重和
public int weightSum() {
return this.weightSum;
}
// 判断最小生成树是否包含图中的所有节点
public boolean allConnected() {
for (int i = 0; i < graph.length; i++) {
if (!inMST[i]) {
return false;
}
}
return true;
}
}