最小生成树之 prim & kruskal

本文介绍了最小生成树的概念,及其在无向连通图中的应用。详细阐述了Kruskal算法和Prim算法的原理与步骤,并通过实例分析了两种算法在寻找最小生成树过程中的差异。Kruskal算法按边排序,避免形成回路;Prim算法则从特定顶点开始,逐步将顶点连入集合中,确保最小权值连接。
摘要由CSDN通过智能技术生成

最小生成树:

       给定一个带权的 n 个结点的无向连通图,选取一个包含原图中的所有 n 个结点,并且保持图连通的,所有边的权值和最小。

求最小生成树的算法:

a:Kruskal 算法

      图的存贮结构采用边集数组,该方法是按边进行连通的,类似一个开始为空的边集,每次选出不在集合中的权重最小的边,判断是否构成回路,若无,将该边加入到边集中,权值相等的边在数组中排列次序可以是任意的,该方法对于边相对比较多的不太实用,会浪费时间。

b:prim 算法

      图的存贮结构采用邻接矩阵。该方法是按各个顶点连通的步骤进行的,需要一个开始为空集的顶点集,之后将已连通的顶点陆续加入到集合中,直到顶点集中中包含了所有顶点,如此得到所需的最小生成树。


Kruskal 算法:

方法:将图(n 个结点)中的所有边按权值从小到大排序,依次拿出一条边,若选边后不形成回路则选择该边,否则除去该边,依次选择(n -1)条边,即得最小生成树。

 

第一步,选择第一条边(权值为1)

第二步:选第二条边(权值为2)

第三步:选第三条边(权值为3)

第四步:选第四条边(权值为4)

第五步:选第五条边(权值为5)(但是要选择当前未连通的,例如3和4的权值也为5但此时3和4 已连通所以就不要选择这条边,而选择2和3


#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110
using namespace std;
int pre[N]={0};
struct node
{
    int r1,r2;
    int p;
}road[N];
int cmp(node a,node b)
{
    return a.p<b.p;
}
int find(int x)
{
    return x==pre[x]?x:pre[x]=find(pre[x]);
}
int main()
{
    int n,m,f1,f2,pm,i,flag;
    while(scanf("%d%d",&n,&m)&&n)
    {
        pm=flag=0;
        for(i=1;i<=m;++i)pre[i]=i;
        for(i=0;i<n;++i)
        {
            scanf("%d%d%d",&road[i].r1,&road[i].r2,&road[i].p);
        }
        //Kruskal算法
        sort(road,road+n,cmp);//排序
        for(i=0;i<n;++i)//选边
        {
            f1=find(road[i].r1);
            f2=find(road[i].r2);
            if(f1!=f2)//若无构成回路则选择该边
            {
                pre[f1]=f2;pm+=road[i].p;//pm为最小生成树的最小权值总和
            }
        }
        for(i=1;i<=m;++i)//判断此时是否为一个合格的最小生成树,即是否连通
        {
            if(i==find(i))flag++;
        }
        if(flag>1)printf("?\n");
        else printf("%d\n",pm);
    }
    return 0;
}

Prim 算法:

方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中

,表示该顶点已连通,然后继续通过此方式选点,直到所有点都加入都集合中,即得最小生成树。

例在下图中从1点出发求最小生成树

先写出其邻接矩阵

第一步:从1开始,1进集合,找与集合外所有点所构成的边中权值最小的边

(1,2)-6  (1,4)-5  (1,3)-1

所以取(1,3)这条边

第二步:3进集合,1,3与2,4,5,6构成的最小边为(1,4)-5  (3,6)-4

所以取(3,6)边

第三步:6进集合,1,3,6与2,4,5构成的各最小边(1,2)-6  (3,2)-5 (6,4)-2

所以取(6,4)边

第四步:4进集合,1,3,6,4与2,5构成的各最小边(1,2)-6  (3,2)-5  (6,5)-6

所以取(3,2)边

第五步:2进集合,1,3,6,2,4与5构成的各最小边(2,5)-3

取(2,5)边

当然这里程序实现的时候要有更新最小权值的步骤

#include<cstdio>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
int m,map[110][110];
int vis[110],rec[110];
void prim()
{
    int mark,minw,cnt=0,fe=0;
    vis[1]=1;
    for(int i=1;i<=m;++i)
        rec[i]=map[1][i];//记录其他点与指定顶点的边的权值
    while(1)
    {
        minw=inf;
        mark=0;
        for(int i=1;i<=m;++i)
        {
            if(!vis[i]&&rec[i]<minw)//未进入集合中且当前权值最小
            {
                minw=rec[i];
                mark=i;
            }
        }
        if(!mark)//若再找不到这样条件的边即退出循环
            break;
        fe+=minw;//权值总和
        cnt++;//所选顶点的个数
        vis[mark]=1;
        for(int i=1;i<=m;++i)
        {
            if(!vis[i]&&rec[i]>map[mark][i])//更新未进入集合的点与集合中的点的最小权值(这里不明白的话要动手自己模拟画一下
                rec[i]=map[mark][i];
        }
    }
    if(cnt!=m-1)//若不包括所有顶点则不是最小生成树
        printf("?\n");
    else
        printf("%d\n",fe);
}
int main()
{
    int n,a,b,f;
    while(scanf("%d%d",&n,&m)&&n)
    {
        memset(vis,0,sizeof(vis));
        memset(rec,0,sizeof(rec));
        memset(map,inf,sizeof(map));
        for(int i=0;i<n;++i)
        {
            scanf("%d%d%d",&a,&b,&f);
            if(f<map[a][b])//邻接矩阵
                map[a][b]=map[b][a]=f;
        }
        prim();
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值