最小生成树
最小生成树(Minimum Spanning Tree,MST)是图论中的一种概念,用于找到连接所有顶点且边权总和最小的树(默认带权无向图)
Kruskal 算法
Kruskal 算法是一种贪心算法,其基本思想是从边出发:每次选择权值最小的边,若该边连接的两个顶点不在同一个连通分量中,则将其加入生成树中,直到生成树包含 \(n-1\) 条边为止
从边出发的视角使用边列表存储图 无向图连通性的判断使用并查集
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
class DS // 并查集
{
vector<int> parent;
public:
explicit DS(int n)
{
parent.resize(n);
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x)
{
if (parent[x] != x)
{
parent[x] = find(parent[x]);
}
return parent[x];
}
bool unite(int x, int y)
{
int xroot = find(x);
int yroot = find(y);
if (xroot == yroot) return false;
parent[xroot] = yroot;
return true;
}
};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m; cin >> n >> m;
vector<tuple<int,int,int>> edges; // 边列表
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
u--; v--; // 根据题目选择 0/1 based
// 只需记录连通性,无需考虑方向
edges.emplace_back(w,u,v);
}
ranges::sort(edges); // 权重放 tuple 第一位
long long ans = 0;
int cnt = 0; // 边数
DS ds(n);
for (auto [w, u, v] : edges)
{
if (ds.unite(u, v))
{
ans += w;
cnt++;
if (cnt == n-1) break;
}
}
if (cnt != n-1) // 不连通
{
cout << "orz"; return 0;
}
cout << ans;
}
- 时间复杂度:\(O(m\log m)\),其中 \(m\) 为边数,瓶颈在于排序
Prim 算法
Prim 算法也是一种贪心算法,其基本思想是从点出发:从任意一个顶点开始,每次选择距离生成树最近的顶点,将其加入生成树中,直到生成树包含 \(n\) 个顶点为止
从点出发的视角使用邻接表存储图 最近点的选择使用优先队列
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m; cin >> n >> m;
vector adj(n, vector<tuple<int,int>>()); // 邻接表
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
u--; v--; // 根据题目选择 0/1 based
adj[u].emplace_back(v,w); // 无向图
adj[v].emplace_back(u,w);
}
// 权重,点号
priority_queue<tuple<int, int>, vector<tuple<int,int>>, greater<>> pq;
pq.emplace(0,0); // 任意点开始,权重设为 0
long long ans = 0;
vector visited(n, false);
int cnt = 0;
while (!pq.empty())
{
auto [w, u] = pq.top();
pq.pop();
if (visited[u]) continue; // 出堆再特判,防死循环
visited[u] = true;
ans += w; // 出堆时更新全局最小权重
cnt++;
if (cnt == n) break;
for (auto [v, ww] : adj[u])
{
if (visited[v]) continue; // 剪枝!
pq.emplace(ww, v);
}
}
if (cnt != n) // 不连通
{
cout << "orz"; return 0;
}
cout << ans;
}
- 时间复杂度:\(O(m\log m)\)
Gemini 3.0 pro 的一句话总结:
- 如果图非常稠密(边多),用朴素版 Prim
- 如果是稀疏图(大多数算法题),用 Kruskal
Prim 与 Dijkstra 算法十分类似,故在该总结中的原理也相同(如有需要请移步这里)
标题:最小生成树
作者:Zwing
创建于:2025-12-16 23:31:21
更新于:2026-08-07 16:17:52
链接:https://zanytriumph.github.io/posts/最小生成树.html
版权声明:本文章采用 CC BY-NC-SA 4.0 进行许可