最小生成树的两种方法

本文介绍了最小生成树问题,包括Prim和Kruskal两种算法。Prim算法以O(n^2)的时间复杂度从一个点出发逐步扩展,而Kruskal算法则依赖边的排序,时间复杂度为O(eloge)。 Prim算法使用low数组记录距离,vis数组防止重复,Kruskal算法则利用并查集判断边的联通性。最小生成树的求解通常是对模板的适当修改,准备一个高效的模板很有帮助。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最小生成树问题的讲道理都是很直观的,让人一眼看得穿,然后再在模版上稍加改动就完成了。

一般来说Prim跟Kruskal方法都是能解题的。但是复杂度不一样。

Prim的时间复杂度是O(n^2),而Kruskal方法与给出的边的数量有关,复杂度为O(eloge)其实时间都是花在排序上了 。

1.Prim算法

简单来说,就是从一个点开始不断寻找,链接着其他相关点,每次加入一个距离最小的点。

其中low数组就是保存到其他点的距离,vis保证每个点只经过一次。

另外求最大生成树类似,只要将输入的数加上负号,最后输出的时候再加上负号。

#include<iostream>
#include<string.h>
using namespace std;
#define N 105
#define inf 0x3f3f3f3f
int low[N],vis[N],G[N][N];
int x,n,ans;
int prim()
{
    int pos,minnum,result=0;
    memset(vis,0,sizeof(vis));
    vis[1]=1;
pos=1;
//从第一个定点开始也可从指定定点开始
    for(int i=1; i<=n; i++)
    {
        if(i!=pos)low[i]=G[pos][i];
}
//更新low数组
    for(int i=1; i<n; i++)//再运行n-1次
    {
        minnum=inf;
        for(int j=1; j<=n; j++)
        {
            if(vis[j]==0&&minnum>low[j])
            {
                minnum=low[j];
                pos=j;
            }
        }
//if(minnum==inf)break;前面在加cnt变量,可以判断是否能成功生成最小生成树
        result+=minnum;//找到最小值与下一点
        vis[pos]=1;
        for(int i=1; i<=n; i++)
        {
            if(vis[i]==0&&low[i]>G[pos][i])
                low[i]=G[pos][i];
        }//更新low数组
    }
    return result;
}
int main()
{
    while(cin>>n)
    {
        memset(G,inf,sizeof(G));//赋初始值inf
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
            {
                cin>>x;
                G[i][j]=G[j][i]=x;
//最大生成树则是把权值改成负数,最终结果再加个负号
            }
        ans=prim();
        cout<<ans<<endl;
    }

    return 0;
}

2.Kruskal算法

首先相对Prim不同的是,这是一种针对边来解决问题的一种方法,因此,定义结构,保存边的两点,权重等信息。

另外,要求最小,必然对权重排序。

然后不断纳入新的联通分量的点。(并查集知识)

#include<iostream>
#include<algorithm>
using namespace std;
struct Edge
{
    int u,v,w;
} edge[105];//结构体包括边的两个连接点和权重
int parent[105];

int findpa(int x)
{
    return parent[x]==x?x:parent[x]=findpa(parent[x]);
}//查找是否在同一个连通分量中

bool cmp(Edge a,Edge b)
{
    return a.w<b.w;
}//按权值排序

void Init()
{
    for(int i=1; i<=105; i++)
        parent[i]=i;//开始每个都是一个单独的联通分量
}


int main()
{
    int n;
    cin>>n;
    Init();
    for(int i=0; i<n; i++)
        cin>>edge[i].u>>edge[i].v>>edge[i].w;
    sort(edge,edge+n,cmp);
    int ans=0;
    for(int i=0; i<n; i++)
    {
        int x=findpa(edge[i].u);
        int y=findpa(edge[i].v);
        if(x!=y)//判断两点是否在同一个连通分量中,是就不要,不是就纳入
        {
            parent[y]=x;
            ans+=edge[i].w;
        }
    }
    cout<<ans<<endl;
    return 0;
}



前导知识:并查集

算法描述:并查集用来解决判断两个节点是否相连通,且不需要给出的具体路径(需要给路径另外考虑dfs等算法)

算法核心:指定唯一根节点,判断两节点是否有共同的根节点。单纯向上寻找可能很费时间,且因为不用保留路径因此可以使用路径压缩:既令同一个连通分量中的元素直接指向父节点。

核心代码 :简单模版

void init()//一开始每个人都是独立一个连通分量
{
    cnt = 0;
    for (int i=0; i<=m; i++) p[i] = i;
}
int find(int x)//不断查找父节点
{
    return p[x]==x ? x : p[x] = find(p[x]);
}
void merge(int a, int b)
{
    int pa = find(a), pb = find(b);
    p[pa] = pb; 
}


PS:关于最小生成树的基本上都是在模版上大致改动。准备一个好用的模版很重要~



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值