最小生成树

定义:

知周所重,一个具有 n 个节点,n - 1条边的连通图,虽然也是图,但有一个更确切的名字,名为”树“.

稍微拓展一下,在一个有 n 个节点的连通图中,将节点间连通的子图,也有可能是树.

因为是连通图,所以一定有路径在连通后形成树.这个时候,这棵树名为生成树.

再多些东西?如果加上边权的话,那么生成树之间边权之和可能会有不同
那么这个时候,边权之和最短的那颗生成树就是我们的最小生成树了.

为什么是"可能会有不同"好人机的问题
如果往极端方面去想,如果图是一条链,那么生成树有且仅只有一条.
如果图边权由01组成,那么即使路线不同,边权也相同(树只有n - 1条边).

实现1—Kruskal

方法步骤:

1.将所有边按边权从小到大排序.

2.选一条边,判断加入这条边是否会形成环.如果不会,则加入该边.
并且将该边所属节点所属的连通块合并.
注:不用担心最优解,因为边权是由从小到大排序.

3.重复过程2,直到所有点都被连通.

看起来挺好写的,但是如何判断是否会形成环呢?

  • 能形成环,说明两个点之间相连.

  • 两个点之间相连,说明在一个连通块内.

  • 在一个连通块内,等于在同一个集合内.

诶,说到集合,就要提到“并查集”这个算法了.
该算法通过记录每个节点的祖先节点,来实现判断节点间是否在一个集合内的操作.
同时,也能快速将两个集合间合并.

n 个节点,m 条边的情况下,如果使用 O(mlogm) 的排序算法,再加上 O(mlogn) 的并查集.
如果不考虑严格复杂度,那么Kruskal的复杂度为O(mlogm).

来看看例题:

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中的堆优化.在加上了优先队列后,
算法最终的复杂度为 O((n+m)logn) .

模板代码:

#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-买礼物:
题目介绍:
又到了一年一度的明明生日了,明明想要买 B 样东西,巧的是,这 B 样东西价格都是 A 元。

但是,商店老板说最近有促销活动,也就是:

  • 如果你买了第 I 样东西,再买第 J 样,那么就可以只花 KI,J 元,更巧的是,KI,J 竟然等于 KJ,I

现在明明想知道,他最少要花多少钱。

解法:
就是道最小生成树的板子题,不过要注意的是,
题目中提到KI,J可能大于 A.所以
如果优惠还不如单个买(那还叫优惠吗?)时,
应直接加上A,而不是 KI,J.

参考文献:

posted @   Cai_hy  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示