Prim 算法 vs Kruskal(库鲁斯卡尔)算法
Prim 算法和 Kruskal 算法都是用于求解 最小生成树(MST,Minimum Spanning Tree) 的经典算法,但它们在实现方式、适用场景和时间复杂度上有所不同。
对比表
对比项 | Prim 算法 | Kruskal(库鲁斯卡尔)算法 |
---|---|---|
基本思想 | 从一个顶点开始,每次选择当前最小的边扩展 MST | 按边权从小到大排序,每次选择不形成环的最小边加入 MST |
算法类型 | 贪心 + 逐步扩展 | 贪心 + 并查集(Disjoint Set) |
数据结构 | 邻接矩阵(O(n²))或 最小堆 + 邻接表(O(m log n)) | 并查集 + 边排序(O(m log m)) |
适用图 | 稠密图(边数多)更优 | 稀疏图(边数少)更优 |
选取方式 | 逐步扩展到最近的未加入 MST 的点 | 直接从所有边中选择最小的 |
是否支持负权边 | 支持 | 支持 |
是否保证连通 | 必须连通才能运行 | 不连通时可用于求最小生成森林 |
时间复杂度 | - O(n²)(邻接矩阵) - O(m log n)(堆优化) | - O(m log m)(排序 + 并查集) |
Prim 算法
思路
- 从任意一个顶点开始,把它加入 MST。
- 选择 与 MST 中的顶点相连 且 权值最小 的边,将其对应的顶点加入 MST。
- 重复上述步骤,直到所有顶点都加入 MST。
实现方式
- 邻接矩阵 + 朴素 Prim(O(n²)):适用于 稠密图(边数多)。
- 邻接表 + 最小堆(O(m log n)):适用于 稀疏图(边数少)。
代码示例
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int INF = INT_MAX;
const int V = 5;
int minKey(vector<int>& key, vector<bool>& inMST) {
int min = INF, min_index;
for (int v = 0; v < V; v++) {
if (!inMST[v] && key[v] < min) {
min = key[v], min_index = v;
}
}
return min_index;
}
void primMST(vector<vector<int>>& graph) {
vector<int> parent(V, -1), key(V, INF);
vector<bool> inMST(V, false);
key[0] = 0;
for (int count = 0; count < V - 1; count++) {
int u = minKey(key, inMST);
inMST[u] = true;
for (int v = 0; v < V; v++) {
if (graph[u][v] && !inMST[v] && graph[u][v] < key[v]) {
parent[v] = u, key[v] = graph[u][v];
}
}
}
cout << "Edge \tWeight\n";
for (int i = 1; i < V; i++) {
cout << parent[i] << " - " << i << " \t" << graph[i][parent[i]] << "\n";
}
}
int main() {
vector<vector<int>> graph = {
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
primMST(graph);
return 0;
}
Kruskal(库鲁斯卡尔)算法
思路
- 按权值排序所有边。
- 逐步选择最小权重的边,如果不形成环,则加入 MST。
- 使用并查集 维护已加入的边,防止形成环。
- 直到 MST 包含
n-1
条边。
实现方式
- 排序 + 并查集(Union-Find)
- O(m log m)(m 为边数,因排序复杂度较高)
代码示例
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Edge {
int u, v, weight;
bool operator<(const Edge& other) const {
return weight < other.weight;
}
};
const int MAX_N = 1000;
int parent[MAX_N], rank_[MAX_N];
void init(int n) {
for (int i = 0; i < n; i++) {
parent[i] = i;
rank_[i] = 1;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
bool unite(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX == rootY) return false;
if (rank_[rootX] > rank_[rootY]) {
parent[rootY] = rootX;
} else if (rank_[rootX] < rank_[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank_[rootX]++;
}
return true;
}
int kruskalMST(vector<Edge>& edges, int V) {
sort(edges.begin(), edges.end());
init(V);
int mstWeight = 0, edgesUsed = 0;
for (const Edge& e : edges) {
if (unite(e.u, e.v)) {
mstWeight += e.weight;
edgesUsed++;
if (edgesUsed == V - 1) break; // 生成 V-1 条边的最小生成树
}
}
return mstWeight;
}
int main() {
int V = 4;
vector<Edge> edges = {
{0, 1, 10}, {0, 2, 6}, {0, 3, 5}, {1, 3, 15}, {2, 3, 4}
};
cout << "Minimum Spanning Tree Weight: " << kruskalMST(edges, V) << endl;
return 0;
}
Prim vs Kruskal:哪个更好?
情况 | 推荐算法 | 原因 |
---|---|---|
稠密图(边多) | Prim | O(n²) 适用于邻接矩阵 |
稀疏图(边少) | Kruskal | O(m log m) 适用于邻接表 |
边的数量 ≈ 顶点² | Prim | 遍历顶点比遍历边快 |
边的数量 ≪ 顶点² | Kruskal | 处理少量边更快 |
需要知道 MST 结构 | Prim | 可以直接记录树 |
需要快速求权值和 | Kruskal | 并查集查找更快 |
总结
- Prim 适用于稠密图,邻接矩阵 O(n²) 或堆优化 O(m log n)。
- Kruskal 适用于稀疏图,排序 + 并查集 O(m log m)。
- 两者均支持负权边,不适用于负环(Dijkstra/Bellman-Ford 适合)。
在实际应用中,Kruskal 适用于道路网络、社交网络等图,Prim 适用于电网、计算机网络等稠密图。