- admin 的博客
克鲁斯卡尔算法 (Kruskal's Algorithm)
- @ 2026-6-7 19:56:44
1. 算法概述
克鲁斯卡尔算法(Kruskal's Algorithm)是一种用于求解最小生成树(Minimum Spanning Tree, MST) 的经典贪心算法。它的核心特点是先将所有边按权重从小到大排序,然后依次考察每条边,如果该边连接的两个顶点当前不在同一个连通分量中(即加入后不会形成环),则将其加入生成树。该算法特别适用于稀疏图(边数较少的图)。
2. 核心思想
- 排序:将所有边按权重升序排列。
- 贪心选择:从小到大遍历每条边。
- 并查集判环:使用并查集(Union-Find)维护顶点的连通性。若一条边的两个端点不在同一集合中,则加入该边不会形成环,将其合并到同一个集合。
- 终止条件:当选出 V-1 条边时,最小生成树构建完成(V 为顶点数)。
3. 算法步骤/伪代码
Kruskal(G):
sort all edges by weight ascending
UF = UnionFind(V)
mst_edges = []
total_weight = 0
for each edge (u, v, w) in sorted edges:
if UF.find(u) != UF.find(v):
UF.union(u, v)
mst_edges.add(edge)
total_weight += w
if mst_edges.size() == V - 1:
break
return total_weight, mst_edges
4. C++ 代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// ==================== 并查集 ====================
class UnionFind {
private:
vector<int> parent;
vector<int> rank;
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
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];
}
void unite(int x, int y) {
int rx = find(x);
int ry = find(y);
if (rx == ry) return;
// 按秩合并
if (rank[rx] < rank[ry]) {
parent[rx] = ry;
} else if (rank[rx] > rank[ry]) {
parent[ry] = rx;
} else {
parent[ry] = rx;
rank[rx]++;
}
}
bool connected(int x, int y) {
return find(x) == find(y);
}
};
// ==================== Kruskal 算法 ====================
struct Edge {
int u, v, weight;
Edge(int u, int v, int w) : u(u), v(v), weight(w) {}
};
int kruskal(int V, vector<Edge>& edges) {
// 按权重升序排序
sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
return a.weight < b.weight;
});
UnionFind uf(V);
int totalWeight = 0;
int edgeCount = 0;
for (const Edge& e : edges) {
if (!uf.connected(e.u, e.v)) {
uf.unite(e.u, e.v);
totalWeight += e.weight;
edgeCount++;
if (edgeCount == V - 1) break;
}
}
// 若边数不足 V-1,说明图不连通
if (edgeCount < V - 1) return -1;
return totalWeight;
}
// ==================== 测试 ====================
int main() {
// 示例:求 LeetCode 1584 连接所有点的最小费用
// 点集:[(0,0), (2,2), (3,10), (5,2), (7,0)]
vector<pair<int, int>> points = {
{0, 0}, {2, 2}, {3, 10}, {5, 2}, {7, 0}
};
int V = points.size();
// 建图:完全图,边权为曼哈顿距离
vector<Edge> edges;
for (int i = 0; i < V; i++) {
for (int j = i + 1; j < V; j++) {
int w = abs(points[i].first - points[j].first) +
abs(points[i].second - points[j].second);
edges.push_back(Edge(i, j, w));
}
}
int result = kruskal(V, edges);
cout << "最小生成树总权重: " << result << endl;
// 期望输出: 20
return 0;
}
运行示例
最小生成树总权重: 20
5. 复杂度分析
- 时间复杂度:O(E log E),其中 E 为边数。
- 排序:O(E log E)
- 并查集操作:近似 O(E · α(V)),α 为反阿克曼函数,可视为常数。
- 空间复杂度:O(V + E),存储边列表和并查集。
当图为稀疏图(E ≈ V)时,复杂度接近 O(V log V),十分高效。
6. 应用场景
| 场景 | 说明 |
|---|---|
| 网络布线 | 连接所有节点且总成本最低 |
| 电路设计 | 最小化 PCB 布线总长度 |
| 聚类分析 | 单链聚类(Single-linkage Clustering) |
| 图像分割 | 基于图的分割算法(Graph-based Segmentation) |
| 交通/通信网络 | 修建公路、光缆的最小总费用 |
7. 经典例题
LeetCode 1584. 连接所有点的最小费用
题目描述:给你一个 points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]。连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的曼哈顿距离:|xi - xj| + |yi - yj|。请返回将所有点连接的最小总费用。
解题思路:将每个点看作图的顶点,任意两点间的曼哈顿距离作为边权。问题转化为求完全图的最小生成树。由于是完全图,边数为 O(n²),Kruskal 算法复杂度为 O(n² log n),在 n ≤ 1000 时可通过。
关键代码要点:
- 生成所有边(n 个点产生 n×(n-1)/2 条边)
- 对所有边按曼哈顿距离排序
- 使用并查集依次加入边,计数达到 n-1 时停止
扩展思考:当 n 较大时,可使用 Prim 算法(朴素 O(n²) 或堆优化)获得更优性能。