【模板】最小生成树

题目链接
最小生成树一般是利用贪心的克鲁斯卡尔算法Prim不会写,克鲁斯卡尔是一个离线算法,首先将边读入,按边值排序,再从小往大加边,若两个端点在一起了,则不加,否则加,直至加了n-1条边,若不行,则无最小生成树。
这是贪心的来想,现在让我们来证明一下

证明

1.令Kruskal算法得到的树为K,有一棵最小生成树T,假设他们不同。
2.找到边权最小的在K但不在T中的边e。
3.把e加入T中,形成一个环,删掉这个环中一条不在K中的边e′,得到新生成树T′。
4.若不存在e′则K存在环,矛盾。
5.若w(e′)>w(e),则T′权值和小于T,矛盾。
6.若w(e′)<w(e),则Kruskal执行时先考虑了e′,由于成环没加入e′,因此在e′之前加入。的边权值均≤w(e′)<w(e).由e的定义,K中边权小于w(e)的边均在T中,说明T中边与e′会成环,矛盾。
7.w(e′) =w(e),在T中用e换掉e′。
8.有限步后可把T变为K且权值不变,因此K就是最小生成树。
证毕

下面画图解释一下
这是读入时的状态
这里写图片描述
将边都去掉
这里写图片描述
下面开始连边
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述
2和4根据并查集可知已经在一个集合中,所以不用连这条边。
这里写图片描述

这里写图片描述
时间:500ms。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int f[5001];
struct node
{
    int x,y,z;
}al[200001];
bool cmp(node a,node b)
{
    return a.z<b.z;
}
int find(int a)
{
    if(f[a]==a)
    {
        return a;
    }
    return f[a]=find(f[a]);
}
int main()
{
    int ans,n,m,i,j,k;
    cin>>n>>m;
    for(i=1;i<=5000;i++)
    {
        f[i]=i;
    }
    for(i=1;i<=m;i++)
    {
        cin>>al[i].x>>al[i].y>>al[i].z;
    }
    sort(al+1,al+1+m,cmp);
    k=n;
    for(i=1;i<=m;i++)
    {
        if(find(al[i].x)==find(al[i].y))
        {
            continue;
        }
        f[find(al[i].x)]=find(al[i].y);
        k--;
        ans+=al[i].z;
        if(k==1)
        {
            break;
        }
    }
    cout<<ans;
    return 0;
}

说完了Kruskal,再说一说prim,prim和迪杰斯特拉等一样,也是采用蓝白点的思想,蓝表示尚未进入生成树,白表示已经进入生成树,下面来说一下流程
1.初始化:min[v]=1<<30,min[1]=0,ans=0.
2.循环
(1)寻找min[u]最小的蓝点u。
(2)将u标记为白点,ans+=min[u]。
(3)遍历与u相邻的蓝点,若w[u][v]<min[v],则w[u][v]=min[v]
3.算法结束,ans即为最小生成树的边权值之和,时间复杂度为O(n^2)。
画图演示一下过程
1.min[1]=0,min[2,3,4,5]=1<<30,ans=0.
这里写图片描述
2.将1变为白点修改2,3,4min值。
这里写图片描述
3.可知min[2]最小,所以将2变为白点,修改3,5的min值
这里写图片描述
4.可知min[3]最小,所以将2变为白点,修改4的min值,此时w[u][v]>min[v],所以不做修改
这里写图片描述
5.将4,5遍历可得到最小生成树。
这里写图片描述
上代码,时间:1200ms。

#include<bits/stdc++.h>
using namespace std;
int ww[5002][5002],qaq,n,m,minn[5002],tot=1,now,ans;
bool v1[5002];
int prim(){
    while(tot<n){
        qaq=1<<30,tot++;
        for(int i=1;i<=n;i++)
            if(!v1[i]&&minn[i]<qaq){
                qaq=minn[i];
                now=i;
            }
        ans+=qaq;
        for(int i=1;i<=n;i++)
            if(minn[i]>ww[now][i]&&!v1[i])
                minn[i]=ww[now][i];
        v1[now]=1;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(ww,0x3f,sizeof(ww));
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        ww[u][v]=ww[v][u]=min(w,ww[u][v]);
    }
    for(int i=1;i<=n;i++)
        minn[i]=ww[1][i];
    v1[1]=1;
    printf("%d",prim());
    return 0;
}

下面来两道简单的题
1.局域网

题解

2.Slim Span

题解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值