最小生成树
定义:
知周所重,一个具有 n 个节点,n - 1条边的连通图,虽然也是图,但有一个更确切的名字,名为”树“.
稍微拓展一下,在一个有 n 个节点的连通图中,将节点间连通的子图,也有可能是树.
因为是连通图,所以一定有路径在连通后形成树.这个时候,这棵树名为生成树.
再多些东西?如果加上边权的话,那么生成树之间边权之和可能会有不同,
那么这个时候,边权之和最短的那颗生成树就是我们的最小生成树了.
为什么是"可能会有不同"?
好人机的问题
如果往极端方面去想,如果图是一条链,那么生成树有且仅只有一条.
如果图边权由01组成,那么即使路线不同,边权也相同(树只有n - 1条边).
实现1—Kruskal
方法步骤:
1.将所有边按边权从小到大排序.
2.选一条边,判断加入这条边是否会形成环.如果不会,则加入该边.
并且将该边所属节点所属的连通块合并.
注:不用担心最优解,因为边权是由从小到大排序.3.重复过程2,直到所有点都被连通.
看起来挺好写的,但是如何判断是否会形成环呢?
-
能形成环,说明两个点之间相连.
-
两个点之间相连,说明在一个连通块内.
-
在一个连通块内,等于在同一个集合内.
诶,说到集合,就要提到“并查集”这个算法了.
该算法通过记录每个节点的祖先节点,来实现判断节点间是否在一个集合内的操作.
同时,也能快速将两个集合间合并.
在
如果不考虑严格复杂度,那么Kruskal的复杂度为
来看看例题:
P3366 【模板】最小生成树
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
。
模板代码:(本人码风不喜勿喷)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct edge{
int to,from,val;//分别为边所连接的节点以及边权.
}Edge[200005];
ll ans;
int fa[5005],n,m,cnt;
bool cmp(edge a,edge b){
return a.val < b.val;
}//排序
int fd(int x){
if(x != fa[x]) return fa[x] = fd(fa[x]);
else return x;
}//寻找祖先节点
void kruskal(){
sort(Edge,Edge + m,cmp);
for(int i = 0;i < m;i++){
int u = fd(Edge[i].from);
int v = fd(Edge[i].to);
if(u == v) continue; //如果在同一集合内则跳过
fa[v] = u;//合并
cnt++;ans += Edge[i].val;
}
return;
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) fa[i] = i;
for(int i = 0;i < m;i++) cin >> Edge[i].from >> Edge[i].to >> Edge[i].val;
kruskal();
if(cnt == n - 1) cout<<ans; //因为树有n - 1条边,所以如果边不够,则代表不连通
else cout<<"orz";
return 0;
}
实现2—Prim
方法实现:
1.建立一个集合,初始里面只有一号点.
2.每次选择一个到集合中节点最近并且未在集合中的点,将其加入集合.(且不能形成环)
3.持续过程2,直到所有点都已被加入集合.
Prim的算法流程与最短路中的Dijkstra流程有些许相似.
并且优化方式也可以采用Dijkstra中的堆优化.在加上了优先队列后,
算法最终的复杂度为
模板代码:
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to,val;
};
vector<edge> G[5005];
struct node{
int id,dis;
node(int a,int b){id = a;dis = b;}
bool operator < (const node &u) const{return dis > u.dis;}
};
int n,m,ans,cnt;
bool done[5005];
priority_queue<node> q;
void prim(){
q.push({1,0});
while(!q.empty()){
node tmp = q.top();q.pop();
if(done[tmp.id]) continue;
done[tmp.id] = 1;ans += tmp.dis;cnt++;
for(int i = 0;i < G[tmp.id].size();i++){
if(done[G[tmp.id][i].to]) continue;
q.push({G[tmp.id][i].to,G[tmp.id][i].val});
}
}
return;
}
int main(){
cin >> n >> m;
while(m--){
int u,v,w;
cin >> u >>v >> w;
G[u].push_back({v,w});
G[v].push_back({u,w});
}
prim();
if(cnt == n) cout<<ans<<'\n';
else cout<<"orz";
return 0;
}
最小生成树例题:
P1194-买礼物:
题目介绍:
又到了一年一度的明明生日了,明明想要买样东西,巧的是,这 样东西价格都是 元。 但是,商店老板说最近有促销活动,也就是:
- 如果你买了第
样东西,再买第 样,那么就可以只花 元,更巧的是, 竟然等于 。 现在明明想知道,他最少要花多少钱。
解法:
就是道最小生成树的板子题,不过要注意的是,
题目中提到可能大于 .所以
如果优惠还不如单个买(那还叫优惠吗?)时,
应直接加上A,而不是.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具