1. 算法概述
Prim算法是一种用于求解加权无向连通图的最小生成树(MST)的贪心算法。
核心思想:
-
从任意顶点开始,逐步扩展生成树
-
每次选择连接树和非树节点的最小权重边
-
直到所有顶点都包含在生成树中
特点:
-
适用于稠密图
-
时间复杂度取决于实现方式
-
总是找到连通图的最小生成树
2. 算法适用条件
✔ 加权无向连通图
✔ 可以处理带权图(权重可以为负)
✖ 不适用于有向图
✖ 图必须连通(否则只能找到连通分量的MST)
3. 算法步骤
初始化阶段
memset(dist, 0x3f, sizeof dist); // 所有距离初始化为∞
dist[1] = 0; // 选择起始点(可以是任意点)
int res = 0; // 存储MST的总权重
主循环过程
for(循环n次){
1. 找到未加入MST的离集合最近的节点t
2. 如果非首次循环且dist[t]=∞,说明图不连通
3. 累加该边权重到res
4. 用t更新其他节点到集合的距离
5. 将t加入MST集合
}
4. 时间复杂度分析
实现方式 | 时间复杂度 | 适用场景 |
---|---|---|
邻接矩阵+遍历 | O(V²) | 稠密图 |
邻接表+堆优化 | O(ElogV) | 稀疏图 |
5. 代码模板(C++)
邻接矩阵版本(稠密图)
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N], dist[N];
bool st[N];
int n, m;
int prim() {
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i++) {
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
for (int j = 1; j <= n; j++)
dist[j] = min(dist[j], g[t][j]);
st[t] = true;
}
return res;
}
堆优化版本(稀疏图)
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII>> heap;
int prim_heap() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
heap.push({0, 1});
int res = 0;
while (!heap.empty()) {
auto t = heap.top();
heap.pop();
int ver = t.second;
if (st[ver]) continue;
st[ver] = true;
res += t.first;
for (auto edge : adj[ver]) {
int j = edge.first, w = edge.second;
if (dist[j] > w) {
dist[j] = w;
heap.push({dist[j], j});
}
}
}
return res;
}
6. 常见问题
Q1:Prim和Kruskal算法如何选择?
-
Prim适合稠密图(邻接矩阵存储)
-
Kruskal适合稀疏图(边排序+并查集)
Q2:如何处理图不连通的情况?
-
检查算法结束后是否所有顶点都被访问
-
如果存在未访问顶点,说明图不连通
Q3:为什么Prim算法不能用于有向图?
-
最小生成树定义基于无向图
-
有向图的最小树形图需要使用Chu-Liu/Edmonds算法
7. 典型例题
-
LeetCode 1584. 连接所有点的最小费用
-
ACWING 858 - Prim算法求最小生成树
8. 优化技巧
-
对于稠密图,使用朴素Prim实现更优
-
使用堆优化时注意避免重复入队
-
输入规模大时使用快速读写
9. 声明
以上笔记仅为学习该算法的学习笔记,如有错误请指出
总结:Prim算法是求解最小生成树问题的经典算法,理解其贪心思想和实现方式对掌握图论算法至关重要。根据图的特点选择合适的实现方式(朴素或堆优化)能显著提高算法效率。