P3366 【模板】最小生成树
解题思路
-
最小生成树(MST)概念:
-
在连通无向图中,找到一棵包含所有顶点的树,使得所有边的权值之和最小
-
如果图不连通,则不存在最小生成树
-
-
Prim算法:
-
贪心算法,从单个顶点开始逐步扩展MST
-
维护一个dis数组记录当前MST到各顶点的最小距离
-
每次选择距离最近的未加入顶点,更新其邻居的距离
-
时间复杂度:O(n²),适合稠密图
-
-
Kruskal算法:
-
贪心算法,按边权从小到大考虑
-
使用并查集来判断是否形成环
-
每次选择不会形成环的最小边加入MST
-
时间复杂度:O(m log n),适合稀疏图
-
-
两种算法的比较:
-
Prim需要邻接表存储图,Kruskal直接处理边列表
-
Prim适合稠密图,Kruskal适合稀疏图
-
在本题数据范围下(N≤5000, M≤2×10⁵),Kruskal更优
-
-
实现注意事项:
-
判断图是否连通:Prim看加入顶点数是否为n,Kruskal看是否选了n-1条边
-
无向图处理:需要添加双向边
-
初始化:距离数组初始化为无穷大,并查集初始化为各自独立
-
-
优化空间:
-
Prim可以用优先队列优化到O(m log n)
-
Kruskal的并查集可以进一步优化路径压缩和按秩合并
-
这两种算法都是求解最小生成树的经典方法,根据图的稠密程度选择合适的算法可以提高效率。本题中由于边数可能很大,Kruskal算法更为合适。
第一部分:Kruskal算法实现
#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 10; struct node { int x, y, z; // 边的两个顶点和权重 }; node t[N]; // 存储所有边 int n, m; // n:顶点数, m:边数 int f[N]; // 并查集父节点数组 int find(int x) { // 并查集查找函数 if(f[x] != x) f[x] = find(f[x]); return f[x]; } void merge(int x, int y) { // 并查集合并函数 int fx = find(x), fy = find(y); f[fy] = fx; } bool cmp(node a, node b) { // 边按权重从小到大排序 return a.z < b.z; } void kruskal() { for(int i = 1; i <= n; i++) f[i] = i; // 初始化并查集 sort(t + 1, t + 1 + m, cmp); // 对边按权重排序 int sum = 0, ans = 0; // sum:已选边数, ans:最小生成树总权值 for(int i = 1; i <= m; i++) { int x = t[i].x, y = t[i].y; if(find(x) != find(y)) { // 如果两个顶点不在同一集合 merge(x, y); // 合并集合 ans += t[i].z; // 累加权值 sum++; if(sum == n - 1) { // 已选n-1条边,完成MST cout << ans; return; } } } cout << "orz"; // 无法形成MST } int main() { cin >> n >> m; for(int i = 1; i <= m; i++) cin >> t[i].x >> t[i].y >> t[i].z; // 读入所有边 kruskal(); return 0; }
第二部分:Prim算法实现
#include<bits/stdc++.h> #define pii pair<int,int> using namespace std; const int N = 1e5 + 10, inf = 0x3f3f3f3f; vector<pii> g[N]; // 邻接表存储图,g[u]存储与u相连的所有边(顶点,权重) int n, m; // n:顶点数, m:边数 int dis[N], vis[N]; // dis:当前最小生成树到各顶点的最小距离, vis:标记是否已加入MST void prim() { memset(dis, inf, sizeof(dis)); // 初始化所有距离为无穷大 dis[1] = 0; // 从顶点1开始构建MST int sum = 0, ans = 0; // sum:已加入的顶点数, ans:最小生成树总权值 for(int i = 1; i <= n; i++) { // 寻找未加入MST的顶点中距离最小的 int minn = inf, pos; for(int j = 1; j <= n; j++) if(dis[j] < minn && vis[j] == 0) minn = dis[j], pos = j; if(minn == inf) break; // 图不连通 vis[pos] = 1; // 标记为已加入 sum++; ans += minn; // 累加权值 // 更新与新加入顶点相邻的顶点的最小距离 for(int j = 0; j < g[pos].size(); j++) { int v = g[pos][j].first, z = g[pos][j].second; if(dis[v] > z) dis[v] = z; } } if(sum != n) { // 如果加入的顶点数不足n,说明图不连通 cout << "orz"; return; } cout << ans; // 输出最小生成树总权值 } int main() { cin >> n >> m; for(int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; g[x].push_back({y,z}); // 无向图,添加双向边 g[y].push_back({x,z}); } prim(); return 0; }
prim的优先队列优化
#include<bits/stdc++.h> #define pii pair<int,int> // 定义pair类型别名,存储(权重,顶点) using namespace std; const int N = 1e5 + 10, inf = 0x3f3f3f3f; // 最大顶点数和无穷大常量 vector<pii> g[N]; // 邻接表存储图,g[u]存储与u相连的所有边(顶点,权重) int n, m; // n:顶点数, m:边数 int dis[N]; // dis数组记录各顶点到MST的最小距离 int vis[N]; // vis数组标记顶点是否已加入MST void prim() { memset(dis, inf, sizeof(dis)); // 初始化所有距离为无穷大 dis[1] = 0; // 从顶点1开始,距离设为0 int sum = 0, ans = 0; // sum:已加入顶点数, ans:最小生成树总权值 // 使用最小堆优化,按距离排序,存储(距离,顶点) priority_queue<pii, vector<pii>, greater<pii>> q; q.push({0, 1}); // 初始放入顶点1,距离0 while(!q.empty()) { int x = q.top().second; // 取出当前距离最小的顶点 q.pop(); if(vis[x]) continue; // 如果已访问过则跳过(避免重复处理) vis[x] = 1; // 标记为已加入MST ans += dis[x]; // 累加边权 sum++; // 已加入顶点数+1 // 遍历当前顶点的所有邻边 for(int i = 0; i < g[x].size(); i++) { int y = g[x][i].first; // 邻接顶点 int z = g[x][i].second; // 边权重 // 如果找到更小的连接距离 if(!vis[y] && dis[y] > z) { dis[y] = z; // 更新距离 q.push({dis[y], y}); // 将新距离加入优先队列 // 注意:这里可能会重复加入同一顶点,但通过vis标记可以过滤 } } } // 输出结果 if(sum != n) cout << "orz"; // 如果加入顶点数不足n,说明图不连通 else cout << ans; // 否则输出最小生成树总权值 } int main() { cin >> n >> m; // 读入图数据 for(int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; g[x].push_back({y, z}); // 无向图,添加双向边 g[y].push_back({x, z}); } prim(); // 执行Prim算法 return 0; }

浙公网安备 33010602011771号