Prim vs Kruskal:
- Prim 算法:适合稠密图,利用优先队列维护可扩展边,直接从顶点构造生成树。
- Kruskal 算法:适合稀疏图,按边权重排序后逐渐合并集合。
做法一:kruskal算法,并查集和元组tuple的运用:
Kruskal 算法思想:
- 边排序:将所有边按照权重从小到大排序。
- 并查集:用于管理节点间的连接情况。并查集可以有效判断两个节点是否已经连通,防止环的形成。
- 逐步构造最小生成树:从权重最小的边开始,若该边连接的两个节点不属于同一个集合,则将其加入生成树。
以下是ac代码:
#include<iostream>
#include<vector>
#include<tuple>
#include<algorithm>
using namespace std;
// 并查集中的查找函数,带路径压缩
int find(vector<int>& fx, int x) {
if (fx[x] != x)
fx[x] = find(fx, fx[x]); // 路径压缩
return fx[x]; // 返回根节点
}
// 并查集中的合并函数
bool join(vector<int>& fx, vector<int>& rank, int x, int y) {
int rootx = find(fx, x);
int rooty = find(fx, y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
fx[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
fx[rootx] = rooty;
} else {
fx[rooty] = rootx;
rank[rootx]++;
}
return true;
}
return false;
}
int main() {
int n, m;
cin >> n >> m;
vector<int> fx(n); // 并查集,存储每个节点的父节点
vector<int> rank(n, 0); // 并查集的秩(用于合并时平衡树的高度)
vector<tuple<int, int, int>> t; // 存储边 (节点1, 节点2, 权重)
// 读取输入的边
for (int i = 0; i < m; i++) {
int w, x, y;
cin >> x >> y >> w;
t.push_back(make_tuple(x - 1, y - 1, w)); // 0基索引
}
// 初始化并查集,每个节点的父节点初始化为自己
for (int i = 0; i < n; i++) {
fx[i] = i;
}
// 按照边的权重进行排序
sort(t.begin(), t.end(), [](const tuple<int, int, int>& a, const tuple<int, int, int>& b) {
return get<2>(a) < get<2>(b); // 按权重排序
});
int ans = 0, edges = 0;
// Kruskal算法构造最小生成树
for (auto& edge : t) {
int weight, x, y;
tie(x, y, weight) = edge; // 解包边的节点和权重
// 如果两个节点不在同一个集合中,则合并它们
if (join(fx, rank, x, y)) {
ans += weight; // 加上该边的权重
edges++;
}
// 如果已经构造出 n - 1 条边的生成树,提前退出
if (edges == n - 1) {
break;
}
}
// 检查是否构造了 n - 1 条边的生成树,判断是否连通
if (edges != n - 1) {
cout << "orz" << endl;
} else {
cout << ans << endl;
}
return 0;
}
做法二:prim算法优先队列(小根堆)的使用:
Prim 算法思想:
- 初始化:从任意一个节点开始,将其加入生成树(标记为已访问)。
- 贪心选择:每次选择连接树中某个节点和未访问节点的最小权重边,加入生成树。
- 重复:重复上述步骤,直到所有节点都被访问。
- 判断连通性:如果最终生成树包含的边数不足 N−1N-1N−1,说明图不连通。
以下是ac代码:
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
using namespace std;
const int INF = 0x7fffffff; // 表示无穷大
// 定义一个结构体来表示边的信息
struct Edge {
int to, weight;
Edge(int _to, int _weight) : to(_to), weight(_weight) {}
// 定义一个比较器,用于优先队列按权重排序
bool operator>(const Edge& other) const {
return weight > other.weight;
}
};
int main() {
int N, M;
cin >> N >> M;
// 邻接表存储图,g[i] 存储的是与节点 i 相连的边 (to, weight)
vector<vector<Edge>> g(N);
// 读取图的边
for (int i = 0; i < M; ++i) {
int x, y, z;
cin >> x >> y >> z;
x--; y--; // 将节点编号转换为从 0 开始
g[x].emplace_back(y, z);
g[y].emplace_back(x, z);
}
// Prim's Algorithm
vector<bool> visited(N, false); // 标记节点是否已被访问
priority_queue<Edge, vector<Edge>, greater<Edge>> pq; // 优先队列,用于选取权重最小的边
int mst_weight = 0; // 最小生成树的总权重
int edges_used = 0; // 计数使用了多少条边
// 从节点 0 开始
pq.push(Edge(0, 0)); // 从 0 号节点开始,初始权重为 0
// 执行 Prim 算法
while (!pq.empty()) {
Edge current = pq.top();
pq.pop();
int node = current.to;
int weight = current.weight;
// 如果当前节点已经被访问过,跳过
if (visited[node]) continue;
// 否则将其标记为已访问
visited[node] = true;
mst_weight += weight;
edges_used++;
// 遍历当前节点的邻居节点
for (Edge& neighbor : g[node]) {
if (!visited[neighbor.to]) {
pq.push(Edge(neighbor.to, neighbor.weight));
}
}
}
// 判断是否生成了一棵包含所有节点的树
if (edges_used == N) {
cout << mst_weight << endl;
} else {
cout << "orz" << endl; // 如果边数不足,说明图不连通
}
return 0;
}