前置知识
图的存储、并查集
什么是最小生成树?
今天要讲解的算法是最小生成树,那么什么是最小生成树呢?由于是面向新手的算法教程,尽量用大白话来讲清楚最小生成树这个定义。
假定有这么一个问题,有6个城市,城市之间有一些道路,修道路需要花费不同的金额,现在要你选择其中一些道路,使得所有城市可以互相到达(联通)且花费最小。这个问题的答案就是最小生成树,我们把这个问题抽象为一张图,每个城市是一个点,城市与城市的道路是一条无向边。
如何求解这个问题?
Prim算法
算法思路:将所有城市分为两个集合,一个集合X是已经在最小生成树中的城市,另外一个集合S是不在最小生成树的城市。一开始所有的城市都在集合S中,集合X为空,第一步任意选择一个点加入X,接着是一个循环,每次选择集合S的城市中离集合X中城市最近的一个城市,加入X中并把这条道路加入最小生成树中直到所有点都加入X中。
如上图,首先X为空集,挑选A作为起始起点,此时有3个城市B、C、D与A联通{A<->{B,C,D}}其权值分别为{6,5,1},我们选择道路A<->C,将C加入X中,此时可选择的边有{A<->{B,D},C<->{B,D,E,F}}同样的我们选择最短一条边,将F加入X,如此反复。
代码
#include<bits/stdc++.h>
using namespace std;
int mp[105][105],st[105],d[105];//mp存储无向边,st表示该城市是否被加入集合X,d表示 集合S到集合X的花费值
int n;
int prim()
{
memset(d,0x3f,sizeof(d));//初始化X无点,集合S和集合X不连通
int res=0;
d[1]=0;//选择1号点加入集合X,花费为0
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(st[j]==0&&(t==-1||d[t]>d[j]))t=j;//如果点在X中就不要重复选了,选择一个集合X外最小的点
}
// cout<<t<<" ";
st[t]=1;//入集合X
res+=d[t];//这条边加入最小生成树中
for(int j=1;j<=n;j++)
d[j]=min(d[j],mp[t][j]);//加入新点后,更新一下两个集合之间的距离
}
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>mp[i][j];
cout<<prim();
}
Kruskal算法
算法思路:将所有边排序,每次选择最短的一条边,如果这条边连接的两个点不在同一个集合中,将两点相连。
代码
#include<bits/stdc++.h>
using namespace std;
struct edge{
int a,b,w;//边的起点和终点以及权值
bool operator <(const edge&t)const{//由小到大排序
return w<t.w;
}
}e[1005];
int p[1005];
int find(int x)//并查集来快速判断两点是否在一个集合
{
if(p[x]!=x)return p[x]=find(p[x]);
return p[x];
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)//读入边
{
int a,b,c;
cin>>a>>b>>c;
e[i]={a,b,c};
}
sort(e+1,e+1+m);//排序
for(int i=1;i<=n;i++)
p[i]=i;
int res=0;
for(int i=1;i<=m;i++)
{
int a,b,c;
a=find(e[i].a),b=find(e[i].b),c=e[i].w;
if(a!=b)p[a]=b,res+=c;//如果两个点不在同一个集合,连上边。
}
cout<<res;
}