Kruskal 最小生成树
💡 核心思想
Kruskal 算法按边权从小到大排序,依次选取不构成环的边加入生成树。判断是否构成环用并查集:如果边的两个端点不在同一集合则可以选。适合稀疏图,代码简洁。
Kruskal 的美妙之处在于它把 MST 问题转化为了"贪心选边 + 并查集判环"。代码极其简洁:排序所有边,依次检查,能连就连。竞赛中 Kruskal 比 Prim 更常用,因为实现简单且在稀疏图上效率高。
🎯 直觉理解
想象你要修路连接所有村庄。你先把所有可能的路按造价排序,从最便宜的开始修。如果修这条路不会形成环路(即两端村庄还没被连通),就修。否则跳过看下一条。最后所有村庄都连通时,总造价最小。
📝 算法流程
- 所有边按权值排序
- 初始化并查集
- 依次取边:若两端不在同一集合则合并,加入 MST
- 选满 n-1 条边停止
$$\text{Kruskal 总复杂度:} O(E \log E) \text{(排序为主)}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(E \log E)$(排序 + 并查集) |
| 空间 | $O(V+E)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
struct Edge { int u, v, w; };
bool operator<(const Edge& a, const Edge& b) { return a.w < b.w; }
int fa[5005];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int main() {
int n, m; cin >> n >> m;
vector<Edge> edges(m);
for (auto& e : edges) cin >> e.u >> e.v >> e.w;
sort(edges.begin(), edges.end());
iota(fa, fa + n + 1, 0);
long long total = 0, cnt = 0;
for (auto& e : edges) {
int pu = find(e.u), pv = find(e.v);
if (pu != pv) { fa[pu] = pv; total += e.w; cnt++; }
if (cnt == n - 1) break;
}
if (cnt < n - 1) cout << "orz" << endl;
else cout << total << endl;
return 0;
}⚠️ 常见坑点
并查集忘记路径压缩导致超时
选完 n-1 条边要提前退出
图不连通时要输出错误信息
边数很多时排序是瓶颈
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3366 最小生成树 | 洛谷 | CSP-S | Kruskal模板 |
| P1546 最短网络 | 洛谷 | CSP-S | MST |