最小生成树(Minimum Spanning Tree, MST)
最小生成树是指在一个 连通无向带权图 中,找到一棵生成树,使得所有边的权值之和最小。常见的算法有:
-
Prim 算法(适合稠密图,时间复杂度
O(V^2)
或O(E log V)
) -
Kruskal 算法(适合稀疏图,时间复杂度
O(E log V)
)
这里我们使用 Kruskal 算法,因为它更容易实现,并且适用于大多数情况。
Kruskal 算法步骤
-
对所有边按权值从小到大排序。
-
初始化并查集(Union-Find),用于检测是否形成环。
-
遍历所有边:
-
如果当前边的两个端点不在同一个集合(不形成环),则加入 MST,并合并这两个集合。
-
否则跳过(避免环)。
-
-
直到选取
V-1
条边(生成树的边数)。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 并查集(Union-Find)实现
class UnionFind {
private:
vector<int> parent, 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 u) {
if (parent[u] != u) {
parent[u] = find(parent[u]); // 路径压缩优化
}
return parent[u];
}
// 合并两个集合(按秩合并)
void unite(int u, int v) {
int rootU = find(u);
int rootV = find(v);
if (rootU == rootV) return; // 已经在同一个集合
// 按秩合并(小树合并到大树)
if (rank[rootU] > rank[rootV]) {
parent[rootV] = rootU;
} else if (rank[rootU] < rank[rootV]) {
parent[rootU] = rootV;
} else {
parent[rootV] = rootU;
rank[rootU]++;
}
}
};
// 边的结构体
struct Edge {
int u, v, weight;
Edge(int u, int v, int weight) : u(u), v(v), weight(weight) {}
// 用于排序(按权值升序)
bool operator<(const Edge& other) const {
return weight < other.weight;
}
};
// Kruskal 算法
vector<Edge> kruskalMST(vector<Edge>& edges, int V) {
sort(edges.begin(), edges.end()); // 按权值排序
UnionFind uf(V); // 初始化并查集
vector<Edge> mst; // 存储 MST 的边
for (const Edge& edge : edges) {
if (uf.find(edge.u) != uf.find(edge.v)) { // 不形成环
mst.push_back(edge); // 加入 MST
uf.unite(edge.u, edge.v); // 合并集合
if (mst.size() == V - 1) break; // 已经选了 V-1 条边
}
}
return mst;
}
int main() {
int V, E;
cout << "输入顶点数和边数: ";
cin >> V >> E;
vector<Edge> edges;
cout << "输入每条边 (u v weight):" << endl;
for (int i = 0; i < E; ++i) {
int u, v, weight;
cin >> u >> v >> weight;
edges.emplace_back(u, v, weight);
}
vector<Edge> mst = kruskalMST(edges, V);
cout << "最小生成树的边:" << endl;
int totalWeight = 0;
for (const Edge& edge : mst) {
cout << edge.u << " - " << edge.v << " : " << edge.weight << endl;
totalWeight += edge.weight;
}
cout << "总权值: " << totalWeight << endl;
return 0;
}
代码讲解
1. 并查集(Union-Find)
用于高效检测环:
-
find(u)
:查找u
的根节点(带路径压缩优化)。 -
unite(u, v)
:合并u
和v
所在集合(按秩合并)。
2. 边的结构体 Edge
存储边的信息:
-
u
、v
:边的两个端点。 -
weight
:边的权值。 -
重载
<
运算符,用于排序。
3. Kruskal 算法核心
-
排序所有边(从小到大)。
-
遍历所有边:
-
如果
u
和v
不在同一个集合(find(u) != find(v)
),则加入 MST。 -
合并
u
和v
的集合(unite(u, v)
)。
-
-
直到选出
V-1
条边(生成树的边数)。
4. 主函数
-
输入顶点数
V
和边数E
。 -
输入所有边,调用
kruskalMST
计算最小生成树。 -
输出 MST 的边及总权值。
示例运行
输入:
复制
下载
输入顶点数和边数: 4 5 输入每条边 (u v weight): 0 1 10 0 2 6 0 3 5 1 3 15 2 3 4
输出:
复制
下载
最小生成树的边: 2 - 3 : 4 0 - 3 : 5 0 - 1 : 10 总权值: 19
总结
算法 | 适用场景 | 时间复杂度 | 空间复杂度 |
---|---|---|---|
Kruskal | 稀疏图 | O(E log V) | O(V + E) |
Prim | 稠密图 | O(V^2) 或 O(E log V) | O(V) |
Kruskal 算法适合大多数情况,代码简洁,容易理解。
解释 vector<Edge> kruskalMST(vector<Edge>& edges, int V)
的返回值类型
1. vector<Edge>
是什么?
-
vector<Edge>
是 C++ 中的 动态数组(vector),存储的元素类型是Edge
(即我们定义的边结构体)。 -
它类似于
int[]
或string[]
,但可以动态调整大小(不需要提前指定长度)。
2. kruskalMST
函数的作用
-
输入:
-
edges
:存储所有边的vector<Edge>
(按权值排序后)。 -
V
:图的顶点数量(用于判断何时停止选边)。
-
-
输出:
-
返回
vector<Edge>
,存储的是 最小生成树(MST)的所有边。
-
3. 为什么返回 vector<Edge>
?
-
Kruskal 算法的结果是 一组边(构成最小生成树)。
-
我们需要返回这些边,以便后续计算总权值或输出具体路径。
-
例如:
cpp
复制
下载
vector<Edge> mst = kruskalMST(edges, V); // 接收返回的 MST 边集
4. 类比理解
-
假设有一个函数
vector<int> getNumbers()
,它返回一组整数:cpp
复制
下载
vector<int> numbers = getNumbers(); // 接收返回的整数列表
-
同理,
kruskalMST
返回的是 边的列表,而不是单个值。
5. 代码中的实际应用
在 main
函数中:
cpp
复制
下载
vector<Edge> mst = kruskalMST(edges, V); // 接收 MST 的边 for (const Edge& edge : mst) { cout << edge.u << " - " << edge.v << " : " << edge.weight << endl; }
-
mst
是一个vector<Edge>
,存储了所有属于最小生成树的边。 -
通过遍历
mst
,可以输出每条边的信息。
总结
部分 | 含义 |
---|---|
vector<Edge> | 返回值类型,表示一个动态数组,存储 Edge 结构体 |
kruskalMST | 函数名,用于计算最小生成树 |
edges | 输入参数,存储所有边的列表 |
V | 输入参数,图的顶点数量 |
返回值 | 最小生成树的所有边(vector<Edge> 类型) |
这种设计使得我们可以 灵活地处理返回的边集,例如:
-
计算总权值。
-
输出具体的生成树结构。
-
进一步分析图的连通性。