一.kruskal(克鲁斯卡尔)算法(易理解)(稀疏图)
这个算法就是将每条边用结构体进行存储下来,(例如egde.start表示边的起点,egde.to标识终点,egde.val 表示终点)接着对每条边进行排序,权值(egde.val)小的在前面,接着我们采用并查集的方法从前面开始枚举 每一条边来记录关系(如果已经存在关系就跳过),最后当建立的关系数等于节点数减一时,说明已经建立好 了最小生成树
例题:洛谷P3366
输入格式
第一行包含两个整数 N,M,表示该图共有 N个结点和 M条无向边。
接下来 M行每行包含三个整数 X,Y,Z,表示有一条长度为 Z的无向边连接结点 X,Y。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m;
struct edge{//储存边
int start,to,val;
}bian[200007];
int f[200007];
int ans=0;
int find(int x){//并查集
if(f[x]==x) return x;
else return f[x]=find(f[x]);
}
bool cmp(edge a,edge b){
return a.val<b.val;
}//排序
int total;
inline void kruskal(){//枚举排好序的每条边
for(int i=1;i<=m;i++){
int u=find(bian[i].start);
int v=find(bian[i].to);
if(u==v) continue;
ans+=bian[i].val;
f[u]=v;
total++;
if(total==n-1) break;//说明已经建立好了最小生成树
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
}
sort(bian+1,bian+m+1,cmp);
kruskal();
if(total==n-1) printf("%d",ans);
else printf("orz");
return 0;
}
二.prime(普利姆)算法
这个算法是现将某个点作为起点向外发散,就是根据寻找距离最小生成树最近的那个点,将其加入最小生成树,最后一步步构造成最小生成树。
一.朴素版prime
#include <bits/stdc++.h>
using namespace std;
int f[5007][5007],dis[5007];//进邻接矩阵路径储存,距离最小生成树的距离
bool k[5007];//是否加入了最小生成树
int main(){
int n,m,x,y,z;
cin>>n>>m;
memset(f,127,sizeof(f));//初始化所有点间的距离最大
for(int i=0;i<m;i++){
scanf("%d%d%d",&x,&y,&z);
if(z<f[x][y]) f[x][y]=f[y][x]=z;//防止出现重边
}
for(int i=1;i<=n;i++){
dis[i]=f[1][i];//现在只有1是最小生成树内的点
}
k[1]=1; //标记
int minindex,sum=0,total=1;
for(int i=1;i<n;i++){
int flag=0;//是否能够找到下一个能加入最小生成树的点
int min=99999;
for(int j=1;j<=n;j++){
if(!k[j]&&dis[j]<min){//寻找距离最小生成树最近的点
flag=1;//找得到
min=dis[j];
minindex=j;
}
}
if(flag==1) total++;//找得到就加一
k[minindex]=1;
sum+=dis[minindex];
for(int j=1;j<=n;j++){
if(!k[j]&&f[minindex][j]<dis[j]){//更新未加入最小生成树的点距离最小生成树的距离。
dis[j]=f[minindex][j];
}
}
}
if(total==n) cout<<sum;
else cout<<"orz";
return 0;
}
二.链式前项星存图
优化了加入新的点后更新未加入的点到最小生成树的距离的时间
#include <bits/stdc++.h>
using namespace std;
int n,m,cnt,ans,now=1,ss=1;//点数,边数,边的编号,答案,当前加入最小生成树的数,实际加入最小生成树的点的个数
bool vis[3000007];//标记数组
int head[3000007],dis[3000007];//head[i]表示以i作为起点的所有距离
struct pre{
int next,val,to;//下一条相关联的边,权值,这条边的终点
}E[3000007];
void add(int u,int v,int w){//增加节点(插入式)
cnt++;//边数加一
E[cnt].next=head[u];//让新插入的边的next指向原本以u开头的边的编号
E[cnt].to=v;
head[u]=cnt;//以u为起点的head指向当前的边
E[cnt].val=w;
}
void prime(){
for(int i=2;i<=n;i++) dis[i]=0x3f3f3f3f;//初始化为无穷,表示都没有关系
for(int i=head[1];i!=0;i=E[i].next){
dis[E[i].to]=min(E[i].val,dis[E[i].to]);//避免重复边
}//以1为起点,开始给dis[i]赋值,先默认表示编号为i的点距离i-1的距离是最小的
for(int i=1;i<n;i++){
bool flag=0 ;//是否能找到点
int minn=0x3f3f3f3f;
vis[now]=1;
for(int j=1;j<=n;j++){
if(minn>dis[j]&&!vis[j]){
flag=1;//能就标记
minn=dis[j];
now=j;
}
}
ans+=minn;
if(flag) ss++;//能就加一
for(int j=head[now];j!=0;j=E[j].next){
if(dis[E[j].to]>E[j].val&&!vis[E[j].to]){
dis[E[j].to]=E[j].val;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
int x,y,z;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);//因为是无向边所以增加两条边;
}
prime();
if(ss==n) cout<<ans;//判断是否能构成最小生成树
else cout<<"orz";
return 0;
}
三.优先队列+链式前项星
1.不需要担心重复的边,因为权值小的在前面。2.将更新dis数组与寻找距离最小生成树最短的点的相结合
#include <bits/stdc++.h>
using namespace std;
int cet,dis[410000],head[410000],n,m,cnt,x,y,z,sum;
struct edge{
int next,to,val;
}e[410000];
bool vis[410000];
void add(int u,int v,int w){//添加节点
e[++cet].next=head[u];
head[u]=cet;
e[cet].to=v;
e[cet].val=w;
}
typedef pair<int,int>pii;//《权值,节点》
priority_queue<pii,vector<pii>,greater<pii> >q;
void prime(){
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n){
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
sum+=d;
vis[u]=1;
for(int i=head[u];i!=0;i=e[i].next){//更新
if(dis[e[i].to]>e[i].val){
dis[e[i].to]=e[i].val,q.push(make_pair(dis[e[i].to],e[i].to));
}
}
}
}
int main(){
memset(dis,127,sizeof(dis));//无穷大
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
prime();
if(cnt==n) printf("%d",sum);
else printf("orz");
return 0;
}