最小生成树

一.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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值