最小生成树(Kruskal 算法)

原理介绍

最小生成树:在一个带权无向连通图中,找到一棵连接所有顶点的树,使得边的权值之和最小。

Kruskal 算法核心思想

Kruskal 算法基于贪心策略,按照边权从小到大依次选择边,若该边连接的两个顶点尚未连通(即加入后不会形成环),则将其加入生成树,直到选出 n-1 条边为止。

算法步骤

  1. 将所有边按权值从小到大排序。
  2. 初始化并查集,每个顶点各自为一个集合。
  3. 遍历排序后的边:
    • 若边的两个端点不在同一集合,则加入生成树,并合并两个集合。
    • 若已在同一集合,则跳过(避免成环)。
  4. 若最终选出的边数等于 n-1,则成功构建最小生成树;否则图不连通,不存在生成树。

时间复杂度:排序 O(m log m),并查集操作近似 O(m α(n)),总复杂度约为 O(m log m)

与 Prim 算法的对比

  • Kruskal 适合稀疏图(边少),因为主要操作是排序。
  • Prim 适合稠密图(边多),可用堆优化至 O((n+m) log n)

以下附上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, m;
struct Edge {
int x, y, w;
Edge(int a, int b, int c) : x(a), y(b), w(c) {}
};
vector<Edge> edges;
vector<int> fa;

bool cmp(Edge a, Edge b) {
return a.w < b.w;
}

int find(int u) {
if (u == fa[u]) return u;
return fa[u] = find(fa[u]);
}
void unite(int u, int v) {
u = find(u);
v = find(v);
if (u != v) fa[u] = v;
}

ll kruskal() {
sort(edges.begin(), edges.end(), cmp);
ll total = 0; // 总权值
int cnt = 0; // 已选边数
for (auto &e : edges) {
int x = find(e.x), y = find(e.y);
if (x != y) {
total += e.w;
cnt++;
unite(x,y);
}
if (cnt == n-1) break;
}
return cnt == n - 1 ? total : -1; // -1 表示不连通
}

int main() {
cin >> n >> m;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
edges.emplace_back(u, v, w);
}
fa.resize(n + 1);
for (int i = 1; i <= n; i++) fa[i] = i;

ll ans = kruskal();
if (ans == -1) cout << "impossible\n";
cout << ans << '\n';
return 0;
}