- admin 的博客
普里姆算法 (Prim's Algorithm)
- @ 2026-6-7 19:57:38
1. 算法概述
普里姆算法(Prim's Algorithm)是一种用于求解最小生成树(MST) 的贪心算法。与 Kruskal 算法不同,Prim 算法以顶点为中心,从一个起始顶点开始,每次选择一条连接"已选顶点集合"与"未选顶点集合"的最小权重边,将对应顶点加入已选集合。该算法特别适用于稠密图(边数较多的图)。
2. 核心思想
- 顶点扩展:从一个任意起点开始,逐步将顶点加入 MST。
- 贪心选择:每次找到一条边 (u, v),其中 u 在已选集合中,v 不在,且边的权重最小。
- 优先队列优化:使用最小堆(priority_queue)维护候选边,每次 O(log E) 取出最小边。
- visited 数组:标记顶点是否已加入 MST,避免重复处理。
3. 算法步骤/伪代码
Prim(G, start):
priority_queue<Edge> pq // 最小堆,按边权排序
visited[V] = false
pq.push({start, start, 0}) // 虚拟起始边
total_weight = 0
edge_count = 0
while pq is not empty and edge_count < V:
(u, v, w) = pq.top(); pq.pop()
if visited[v]: continue
visited[v] = true
total_weight += w
edge_count++
for each neighbor (next, weight) of v:
if not visited[next]:
pq.push({v, next, weight})
return total_weight
4. C++ 代码实现
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
// ==================== 优先队列优化版 Prim 算法 ====================
struct Edge {
int to, weight;
Edge(int t, int w) : to(t), weight(w) {}
};
// 用于优先队列的比较器
struct Node {
int vertex, weight;
Node(int v, int w) : vertex(v), weight(w) {}
bool operator>(const Node& other) const {
return weight > other.weight; // 小顶堆
}
};
int prim(int V, vector<vector<Edge>>& graph, int start = 0) {
vector<bool> visited(V, false);
priority_queue<Node, vector<Node>, greater<Node>> pq;
// 将起点加入优先队列
pq.push(Node(start, 0));
int totalWeight = 0;
int edgeCount = 0;
while (!pq.empty() && edgeCount < V) {
Node node = pq.top();
pq.pop();
if (visited[node.vertex]) continue;
visited[node.vertex] = true;
totalWeight += node.weight;
edgeCount++;
// 遍历邻接边,将未访问的邻居加入队列
for (const Edge& e : graph[node.vertex]) {
if (!visited[e.to]) {
pq.push(Node(e.to, e.weight));
}
}
}
// 若未访问全部顶点,说明图不连通
if (edgeCount < V) return -1;
return totalWeight;
}
// ==================== 朴素 O(V²) 版 Prim(适合稠密图) ====================
int primDense(int V, vector<vector<int>>& adjMatrix) {
vector<bool> visited(V, false);
vector<int> minDist(V, INT_MAX);
minDist[0] = 0;
int totalWeight = 0;
for (int i = 0; i < V; i++) {
// 选取未访问中 minDist 最小的顶点
int u = -1;
for (int j = 0; j < V; j++) {
if (!visited[j] && (u == -1 || minDist[j] < minDist[u])) {
u = j;
}
}
if (minDist[u] == INT_MAX) return -1; // 不连通
visited[u] = true;
totalWeight += minDist[u];
// 更新邻居的 minDist
for (int v = 0; v < V; v++) {
if (!visited[v] && adjMatrix[u][v] < minDist[v]) {
minDist[v] = adjMatrix[u][v];
}
}
}
return totalWeight;
}
// ==================== 测试 ====================
int main() {
// 示例:5 个顶点,无向带权图
// (0)
// 2 / | \ 3
// (1) | (2)
// 4 \ | / 6
// (3)---(4)
// 1
int V = 5;
vector<vector<Edge>> graph(V);
// 添加边(无向图,需添加双向)
auto addEdge = [&](int u, int v, int w) {
graph[u].push_back(Edge(v, w));
graph[v].push_back(Edge(u, w));
};
addEdge(0, 1, 2);
addEdge(0, 2, 3);
addEdge(0, 3, 6);
addEdge(1, 3, 4);
addEdge(2, 3, 6);
addEdge(3, 4, 1);
int result = prim(V, graph, 0);
cout << "最小生成树总权重 (堆优化): " << result << endl;
// 期望输出: 10 (边: 0-1(2), 0-2(3), 3-4(1), 1-3(4))
// 测试朴素版
vector<vector<int>> adjMatrix(V, vector<int>(V, INT_MAX));
for (int i = 0; i < V; i++) adjMatrix[i][i] = 0;
adjMatrix[0][1] = adjMatrix[1][0] = 2;
adjMatrix[0][2] = adjMatrix[2][0] = 3;
adjMatrix[0][3] = adjMatrix[3][0] = 6;
adjMatrix[1][3] = adjMatrix[3][1] = 4;
adjMatrix[2][3] = adjMatrix[3][2] = 6;
adjMatrix[3][4] = adjMatrix[4][3] = 1;
int result2 = primDense(V, adjMatrix);
cout << "最小生成树总权重 (朴素版): " << result2 << endl;
return 0;
}
运行示例
最小生成树总权重 (堆优化): 10
最小生成树总权重 (朴素版): 10
5. 复杂度分析
- 堆优化版时间复杂度:O((V + E) log V)
- 每个顶点入队一次、出队一次:O(V log V)
- 每条边被检查一次并可能入队:O(E log V)
- 朴素版时间复杂度:O(V²) —— 适合完全图或接近完全图的稠密场景。
- 空间复杂度:O(V + E),存储邻接表和 visited 数组。
| 图类型 | 推荐算法 | 复杂度 |
|---|---|---|
| 稀疏图 (E ≈ V) | Kruskal | O(E log E) |
| 稠密图 (E ≈ V²) | Prim (朴素) | O(V²) |
| 中等图 | Prim (堆优化) | O((V+E) log V) |
6. 应用场景
| 场景 | 说明 |
|---|---|
| 城市管网设计 | 供水/供气管道的最短总长度规划 |
| 集成电路布线 | 最小化芯片上各模块间的连接线总长 |
| 近似算法 | 旅行商问题(TSP)的 MST 近似解 |
| 图像处理 | 最小生成树分割(MST-based Segmentation) |
| 网络广播 | 最小化广播消息的总传输代价 |
7. 经典例题
最小生成树模板题(洛谷 P3366 / POJ 1258)
题目描述:给出一个无向图,求出最小生成树。如果图不连通,输出 orz。
解题思路:
- 选用 Prim 堆优化版处理一般稀疏/中等图。
- 如果最终访问的顶点数 < V,说明图不连通,返回 -1。
- 对于完全图场景(如 POJ 1258 Agri-Net,n ≤ 100,给出邻接矩阵),直接用朴素 O(V²) 版。
代码要点:
- 优先队列存储
{顶点, 到MST的最小边权}。 visited数组避免重复处理。- 出队时若已访问则跳过(lazy deletion 技巧)。
对比 Kruskal
- Kruskal:基于边排序 + 并查集,适合边数少的稀疏图,实现直观。
- Prim:基于顶点扩展 + 优先队列,适合边数多的稠密图,可优化到 O(V²) 朴素版。
- 两者都是经典的 MST 贪心算法,理论上等价,实际选用取决于图的密度。