prim算法详解+例题 --还是畅通工程

Description

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。 

 

Input

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。 
当N为0时,输入结束,该用例不被处理。 

 

Output

对每个测试用例,在1行里输出最小的公路总长度。 

 

Sample Input

 

3

1 2 1

1 3 2

2 3 4

4

1 2 1

1 3 4

1 4 1

2 3 3

2 4 2

3 4 5

0

 

Sample Output

 

3

5

 

 

首先什么是最小生成树:在一个带权值的无环的图中,通过最小的"权值和"把所有的点都连接起来的树,就是最小生成树."带权值"的意思就是假设从A点走到B点需要花费10分钟,那么这个10就是A-B的权值.

prim算法的思想其实就是从一个图中任意选取一个点,和这个点一定有边相连而且不止一条,接下来我们就要去比较这些边的权值了,"最小生成树",如果每条边的值最小,那么最后生成的树的值也就是最小的,说到这里,很多人会说,这不就是"贪心"嘛,没错,这就是一种贪心的想法,我们找到一条边的值最小,假设初始点为A,和A相连的一条权值最小的边的另一头连着B,那么我们新开一个集合C[ ],把A和B添加进去,这时候我们要找下一个了,这时候我们要考虑的是和A,B两个点相连的边中权值最小的,这里要注意一个点,刚才添加的不能算了,接下来找的边不能和C[ ]中的点构成环.循环下去,不断更新,就可以得到一个最小生成树.具体代码详解在下面代码中.

我们先看一下代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define MAX 0x3f3f3f3f
int map[107][107],closest[107];
int n,m;
void prim(int s)
{
    int i,j,sum=0;
    for(i=1; i<=m; i++)
        closest[i]=MAX;
    for(i=1; i<=m; i++)
        closest[i]=map[s][i];
    closest[s]=0;
    int now;
    for(i=1; i<=m; i++)
    {
        now=MAX;
        int min=MAX;
        for(j=1; j<=m; j++)
            if(closest[j]&&closest[j]<min)
            {
                min=closest[j];
                now=j;
            }
        if(now==MAX)
        {
            break;
        }
        sum+=min;
        closest[now]=0;
        for(j=1; j<=m; j++)
            if(map[now][j]&&map[now][j]<closest[j])
                closest[j]=map[now][j];
    }
    //cout<<"***"<<i<<"***"<<endl;
    if(i<m)
        cout<<"?"<<endl;
    else
        cout<<sum<<endl;
}
int main()
{
    while(cin>>n)
    {
        if(n==0)
            return 0;
        m=n;
        for(int i=1; i<=m; i++)
            for(int j=1; j<=m; j++)
                map[i][j]=MAX;
        for(int i=1; i<=n*(n-1)/2; i++)
        {
            int a,b,s;
            cin>>a>>b>>s;
            if(s<MAX)
            {
                map[a][b]=s;
                map[b][a]=s;
            }
        }
        prim(1);
    }
    return 0;
}

下面一段代码首先是对整个map存储空间进行赋初值MAX,MAX的值无穷大.因为是一个无向连通图,你输入一条A-B的权值,其实相当于同时添加一条B-A.所以第三个for循环要这样写,至于那个s<MAX的判定,其实有没有无所谓啦.

for(int i=1; i<=m; i++)
            for(int j=1; j<=m; j++)
                map[i][j]=MAX;
        for(int i=1; i<=n*(n-1)/2; i++)
        {
            int a,b,s;
            cin>>a>>b>>s;
            if(s<MAX)
            {
                map[a][b]=s;
                map[b][a]=s;
            }
        }

因为我存储的时候是从map[1][1]开始存储的,所以prim[1].其实从哪个位置开始都可以.接下来就是最重要的被调函数了:首先定义的sum就是最后的权值总和.

int map[107][107],closest[107];
int n,m;
void prim(int s)
{
    int i,j,sum=0;//sum用来存储最终的权值和
    for(i=1; i<=m; i++)//这里循环次数是m次,因为有m个点
        closest[i]=MAX;//这里对储存最小生成树的数组赋初值
    for(i=1; i<=m; i++)
        closest[i]=map[s][i];//这里将主函数传过来的s点与其他各个点的线路权值存储到closest数组里
    closest[s]=0;//清零是因为自己到自己没有权值,而且是无回路问题;这里相当于把第一个点加入到最小生成树中.
    int now;//定义临时变量记录是否找到权值最小的边的另外一头连接的点,存储新的加入最小生出树的点
    for(i=1; i<=m; i++)
    {
        now=MAX;//赋值为MAX,方便后边进行判断
        int min=MAX;//赋值后方便比较大小,在数据较大时,不会出错
        for(j=1; j<=m; j++)
            if(closest[j]&&closest[j]<min)//如果权值为0,说明已经比较过了(这里在下边的代码中会写出),那么不会进行比较,并且权值要小于当前最小值,有时候会被第一次循环搞糊涂.
            {
                min=closest[j];//更新当前最小值,
                now=j;//更新当前最小值点
            }
        if(now==MAX)
        {
            break;
        }
        sum+=min;//把已经找到的最小权值加入到最终的总权值中
        closest[now]=0;//把当前最小权值点加入到最小生成树中,将它的权值赋0
        for(j=1; j<=m; j++)//这里再做一次更新,因为你已经找到一个新的点,这个新的点又包含了新的边,所以需要再次更新closest数组
            if(map[now][j]&&map[now][j]<closest[j])//因为之前已经将closest中相关点的权值赋值为0,所以再次更新的时候不会改变之前已经找到的点
                closest[j]=map[now][j];
    }
    //cout<<"***"<<i<<"***"<<endl;
    if(i<m)
        cout<<"?"<<endl;
    else
        cout<<sum<<endl;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值