最小生成树和最大生成树
1 生成树概念
生成树(spanning tree) :一个连通无向图的生成子图,同时要求是树。也即在图的边集中选择n-1条,将所有顶点连通。
2 最小生成树
2.1 定义
最小生成树为一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
我们定义无向连通图的 最小生成树 (Minimum Spanning Tree,MST)为边权和最小的生成树。
例题(洛谷):【模板】最小生成树
2.2 kruskal实现
Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。该算法的基本思想是从小到大加入边,是个贪心算法。
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[5005],add=1;
bool cmp(edge a,edge b){return a.w<b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<5005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
e[i]=(edge){a,b,c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<"orz";//不连通
else cout<<ans;
return 0;
}
2.3 prim实现
Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。
实现:
具体来说,每次要选择距离最小的一个结点,以及用新的边更新其他结点的距离。
其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,可以暴力找也可以用堆维护。
堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 decrease-key 的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal 算法,在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但 不一定 实际跑得更快。
还未进行代码实现,之后补上。
3 最大生成树
3.1 定义
和最小生成树类似,只不过在一个图的所有生成树中边权值和最大的生成树即为最大生成树。
例题为学校DS网站上的题目,和最小生成树的题目差不多。
3.2 实现
1、将图中所有边的边权变为相反数,再跑一遍最小生成树算法。相反数最小,原数就最大。
2、修改一下最小生成树算法:对于kruskal,将“从小到大排序”改为“从大到小排序”;
对于prim,将“每次选到所有蓝点代价最小的白点”改为“每次选到所有蓝点代价最大的点”
kruskal实现代码
- 将边权从大到小排序。
/*用Kruskal实现最大生成树*/
/*将边权从大到小排序的Kruskal*/
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[1005],add=1;
bool cmp(edge a,edge b){return a.w>b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
if(find(a)!=find(b))f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<1005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
if(a>b)swap(a,b);
e[i]=(edge){a,b,c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<-1;
else cout<<ans;
return 0;
}
- 或者将每个边的权值变成相反数,跑一遍最小生成树。
/*用Kruskal实现最大生成树*/
/*或者将每个边的权值变成相反数,跑一遍最小生成树*/
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[1005],add=1;
bool cmp(edge a,edge b){return a.w<b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
if(find(a)!=find(b))f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<1005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
if(a>b)swap(a,b);
e[i]=(edge){a,b,-1*c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<-1;
else cout<<-1*ans;
return 0;
}