最小生成树的两种方法

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

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

一般来说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:关于最小生成树的基本上都是在模版上大致改动。准备一个好用的模版很重要~



最小生成树两种常见算法Prim算法Kruskal算法,以下是二者的对比: ### 算法核心思想 - **Prim算法**:直接合并各点。先把各点的距离设为无限大,各点标记为零,接着遍历各点,找到与标记为一的点距离最近的点,更新该点标记为一,再把其他“零点”到“一点”的距离更新为最短距离,逐步扩展生成最小生成树 [^4]。 - **Kruskal算法**:将图中所有边按权值从小到大排序,依次选取权值最小的边,若该边加入后不会形成环,则将其加入最小生成树,直至包含图中所有顶点。 ### 复杂度 - **Prim算法**:算法复杂度为$O(n^2 + m)$,其中$n$是顶点数,$m$是边数。对于稠密图(边数较多),Prim算法效率较高 [^4]。 - **Kruskal算法**:复杂度主要取决于排序边的复杂度,通常为$O(m log m)$,更适合稀疏图(边数较少)。 ### 适用性 - **Prim算法**:适用于求解无向图中的最小生成树 [^2]。 - **Kruskal算法**:同样适用于无向图,在处理稀疏图时优势明显。 ### 唯一性 - **Prim算法**:生成的最小生成树可能不唯一,当存在多条权值相同的边可供选择时,可能得到不同的最小生成树 [^3]。 - **Kruskal算法**:也可能生成不唯一的最小生成树,原因与Prim算法类似,在权值相同的边选择上存在多种可能 [^3]。 ### 代码示例(Prim算法) ```cpp #include<iostream> #include<cstring> #include<cmath> using namespace std; int st[1009][1009]; int main() { int n,m; cin>>m>>n; int x,y; for(int i=1;i<=n;++i){ cin>>x>>y; cin>>st[x][y]; st[y][x]=st[x][y]; } int p[1009]={0}; int v[1009]; memset(v,0x7f,sizeof(v)); v[1]=0; int maxv=0; for(int i=1;i<=m;++i){ int k=0; int j; for(j=1;j<=m;++j){ if(!p[j]&&v[k]>v[j])k=j; } p[k]=1; maxv=max(maxv,v[k]); for(j=1;j<=m;++j){ if(!p[j]&&st[j][k]<v[j]&&st[j][k]!=0)v[j]=st[j][k]; } } cout<<m-1<<" "<<maxv<<endl; return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值