最小生成树

1 最小生成树的概念

最小生成树(Minimum Spanning Tree,简称MST)指一个联通无向图中包含所有顶点的一棵(也就是没有环),且该树所有边的边权最小

例如,对于以下的图:

最小生成树(用红线标记)为:

2 求解最小生成树

最小生成树模板题
最小生成树主要有两种算法:Prim算法和Kruskal算法。

2.1 Prim算法

2.1.1 求解过程

个人认为,Prim算法与最短路中的Dijkstra算法很像。

我们需要维护一个数组 \(dis\),表示已用点到未用点的最短距离,记得全部初始化为 \(\infty\)。每次我们选择任意节点,遍历节点 \(1\sim n\),选出最小值(是不是跟Dijkstra很像)并将这个节点加入最小生成树,更新 \(ans\),然后更新 \(dis\) 数组。

时间复杂度:\(O(n^2+m)\)(其实还能使用优先队列优化,这里就不讲了)

2.1.2 代码

#include<bits/stdc++.h>
using namespace std;
struct node{
	int v;
	int w;
};
vector<node> p[114514];
int n,m,ans;
int dis[114514]; 
bool flag[114514];//flag数组用来判断该节点是否加入最小生成树
void prim(){
	dis[1]=0;
	for(int i=1;i<=n;i++){
		int minn=INT_MAX;
		int pos=-1;
		for(int j=1;j<=n;j++)
			if(!flag[j]&&dis[j]<minn)
				minn=dis[j],pos=j;//选最小值
		if(pos==-1){
			ans=INT_MAX;
			break;
		}
		flag[pos]=1;//将pos点加入最小生成树
		ans+=minn;//更新ans
		for(int j=0;j<p[pos].size();j++){
			int v=p[pos][j].v;
			int w=p[pos][j].w;
			if(!flag[v]&&w<dis[v]) dis[v]=w;//更新dis数组
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) dis[i]=INT_MAX;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		p[u].push_back({v,w});
		p[v].push_back({u,w});
	}
	prim();
	if(ans!=INT_MAX) cout<<ans;
	else cout<<"orz"; 
	return 0;
}

2.2 Kruskal算法

前置知识:并查集

2.2.1 Kruskal算法求解过程

由于Kruskal算法需要并查集来维护合并,于是先写两个并查集函数:

int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}//查找“祖先”(使用路径压缩)
void join(int c1,int c2){
	int x=find(c1),y=find(c2);
	if(x!=y) fa[x]=y;
}//合并集合

我们还需要一个变量 \(ltk\) 表示当前联通块个数,初始化为 \(n\),每次合并集合则 \(ltk--\),当 \(ltk=1\) 时,代表已经求出最小生成树。

首先,需要将节点按边权从小到大排序,保证每次选择的边一定是当前没被选中中最短的。然后循环 \(m\) 次来求解最小生成树,如果当前边的两个节点不在同一个集合中(由于这两个节点不在同一集合,就不会出现环,两个节点在同一集合合并才会出现环),则合并。

2.2.2 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int fa[314514];
int n,m,ltk,ans;
struct node{
	int u,v;
	int w;
}a[314514];
bool cmp(node a,node b){
	return a.w<b.w;
}//比较函数
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}//查找“祖先”(路径压缩)
void join(int c1,int c2){
	int x=find(c1),y=find(c2);
	if(x!=y) fa[x]=y,ltk--;
}//合并集合
int Kruskal(){
	for(int i=1;i<=m;i++){
		if(find(a[i].u)!=find(a[i].v)){
			join(a[i].u,a[i].v);
			ans+=a[i].w;
		}//判断并合并集合
		if(ltk==1) return ans;
	}
	return -1;
}
signed main(){
	cin>>n>>m;
	ltk=n;
	for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化
	for(int i=1;i<=m;i++) cin>>a[i].u>>a[i].v>>a[i].w;
	sort(a+1,a+m+1,cmp);//排序
	if(Kruskal()!=-1) cout<<ans;
	else cout<<"orz";
	return 0;
}

3 推荐习题

posted @ 2025-04-26 11:49  Loyal_Soldier  阅读(98)  评论(2)    收藏  举报