交互式图论学习
交互式图论学习
Guest User
Using app without sign in
最小生成树计算器
通过从单个顶点生长找到最小生成树
选择算法并生成步骤以开始可视化
Prim 算法为加权无向图构造最小生成树(MST),即以尽可能小的总权重连接每个顶点的边子集。它从任意起始顶点出发生长单棵树,每次都加入连到新顶点的最便宜的边。
算法维护一个优先队列,存放从树跨越到图其余部分的边。每一步取出权重最小的跨越边,把它的新端点加入树,并将该顶点的边插入队列。MST 的割性质保证每条被选的边都属于某棵最小生成树。用二叉堆时运行时间为 O(E log V)。
Prim 算法用于设计低成本网络:电网、光纤与电信布线、供水管道和芯片布线。它也支持聚类与图像分割。面试中常与 Kruskal 算法搭配,用来考查对贪心正确性论证的理解。
Prim 从任意一个起点开始生长出单棵树。每一步都选取恰好有一个端点已在树内的最便宜的边,仅凭这一条规则就足以保证最优,并且永远不需要回溯。
Prim(图, 起点):
树内 = {起点}
pq = 含起点所有出边的优先队列
mst = []
当树内尚未包含所有结点时:
(u, v, w) = pq.取出最小()
若 v 已在树内: 跳过 // 过期条目
mst.追加((u, v, w))
树内.加入(v)
对每条边 (v, x, w2):
若 x 不在树内: pq.插入((v, x, w2))正确性依赖于割性质:对结点的任意一种二分划分,跨越该割的最便宜的边必定属于某棵最小生成树。Prim 把这条性质用在「已在树内」与「尚未加入」这道割上,因此每次取跨越割的最便宜边总是安全的。这条性质也解释了为何完全不需要回溯:一条已被接受的边,绝不会因为后来出现的信息而变成错误选择,这一点与 TSP 之类的问题截然不同。
在一个小型加权图上从 A 开始生长最小生成树,此处贪心选择会刻意舍弃一条看似更便宜的直接边。
示例图: 无向边 A-B (2)、A-C (3)、B-C (1)、C-D (4) 和 B-D (7)。
最小生成树由 A-B、B-C 和 C-D 构成,总权重为 2 + 1 + 4 = 7。注意它恰好有三条边,比四个结点少一条,任何生成树都必然如此。还要注意这里的树是一条链,这提醒我们最小生成树并不是最短路径树:树中从 A 到 D 的距离是 7,而图中最短路径 A 到 C 到 D 为 3 + 4 = 7,A 到 B 到 D 则为 9。两个问题优化的是不同的量,把二者混为一谈是这一主题上最常见的概念性错误。
时间: O(E log V) · 空间: O(V + E)
使用二叉堆时,每条边至多被插入一次、取出一次,各为 O(log E);由于 E 至多为 V 的平方,log E 与 log V 只相差常数因子,因此总体为 O(E log V)。每个结点恰好被加入树一次,这把外层循环的迭代次数限制在 V 次。若改用斐波那契堆并以减小键值代替惰性插入,界限可改进到 O(E + V log V),在稠密图上渐进更优,但常数因子通常使其在实践中并不划算。在非常稠密的图上,最朴素的做法反而胜出:维护一个数组记录到每个树外结点的最便宜边,每轮扫描一次即可,复杂度为 O(V 的平方),当 E 接近 V 的平方时就优于 O(E log V)。因此数据结构的选择取决于稠密程度,而非某种一概而论的偏好。
Prim 与 Kruskal 都能给出最小生成树。选哪个取决于稠密程度、边的到达顺序以及图是否连通。
| 替代算法 | 以下情况更合适 | 代价 |
|---|---|---|
| Kruskal 算法 | 稀疏图,或边已按权重排好序。它用并查集生长出一片森林而非单棵树。 | O(E log E) |
| Boruvka 算法 | 你需要并行化。每个连通分量同时选出自己最便宜的出边。 | O(E log V) |
| Prim 加数组扫描 | E 接近 V 的平方的稠密图。完全避开堆的开销。 | O(V^2) |
| Dijkstra 算法 | 你真正想要的是从某个源点出发的最短路径,而不是权重最小的生成结构。形式相近,目标不同。 | O((V + E) log V) |
阅读完整文章: Minimum Spanning Trees: Prim, Kruskal and Boruvka
相关算法: 克鲁斯卡尔最小生成树算法, 博鲁夫卡算法, 迪杰斯特拉算法