PTA 公路村村通(C语言版本)思路+代码实现

R7-4 公路村村通

作者 陈越

单位 浙江大学

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:

输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:

输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:

6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3

输出样例:

12

代码长度限制

16 KB

时间限制

400 ms

内存限制

64 MB


思路:本题是考察数据结构的部分中最小生成树的知识

(最小生成树:在给定一张无向图,如果在它的子图中,任意两个顶点都是互相连通,并且是一个树结构,那么这棵树叫做生成树。当连接顶点之间的边有权重时,边的权重之和最小的树结构为最小生成树)

图的最小生成树有 Kruskal算法(克鲁斯卡算法)和 Prim算法(普里姆算法)。

这里我是用的Kruskal算法(克鲁斯卡算法)实现的。

这里我用我自己的理解简单介绍一下Kruskal算法(克鲁斯卡算法)。这是一种贪心算法,总的策略:我们在未被加入到图中的边中,选取权值最小的边,且确保加入该边后该图不会形成回路,那么就将该边加入到图中。如果该边是未被加入到图中权值最小的边,但加入该边后,图形成回路,我们就放弃该边,继续检索下一条边,共加入n-1条边结束。(我们知道,一个具有n个顶点的图,最少需要n-1个顶点就可以将该图全部连通,我们这里求最小生成树,当然是希望权值之和尽可能的小,那我们所取的边数也要尽可能少,就取n-1条边)

具体步骤

1.我们将构成图的所有边用结构体保存下来(结构体中有该边的 起点,终点,权值),根据边的权值从小到大将边进行排序,放到一个存放边的结构体的数组中(我们称为边数组)。

2.再设立一个int型的标号数组(biaohao[ n ]),将标号数组初始化 biaohao[i]=i; 这里的i代表的是顶点。

这里我们是利用了并查集的思想,认为标号相同的顶点是已经连通的。在图中没有边加入时,我们依次给每个顶点一个标号。一旦一条连接顶点1和顶点3的边被加入到图中,我们将标号较大的顶点的标号改为标号较小的顶点的标号,即这里将顶点3的标号改为顶点1,即biaohao[ 2 ]= 0(我的biaohao数组是从0开始存储的);(这样我们就可以在检索新的边的时候,只根据检索到的边的两个顶点的标号,来判断是否要将该边加入到图中。(如果该边起点终点标号相同,证明在加入这条边以前这两个顶点已经连通,如果再加入这条边,图将构成回路。如果该边起点终点标号不相同,证明在加入这条边以前这两个顶点未连通,我们可以加入该边)这里还应注意的是,当我们每改变一个顶点的标号的时候,我们应该再检索所有顶点的标号,因为可能存在biaohao[ 4 ]=2,即顶点5在此之前已经和顶点3相连,我们要把所有,与顶点3的标号相同的顶点的标号一起改为顶点1的标号,此时1 3 5 三个顶点标号均为 0,证明这三个顶点已经连通)

3.然后依次从边数组中检索边,对该检索的边的两个顶点进行是否已经连通的判断。若未连通,将顶点标号按要求修改,将该边加入到图中,即sum(已经生成的 树 的总权值)加上该边的权值,然后继续检索下一条边;若已经连通,直接检索下一条边。直到我们将n-1条边加入到了图中,就生成了最小生成树。


 

#include<stdio.h>
int biaohao[1001];//标号数组
int maxx(int a,int b){
    if(a>b){
        return a;
    }
    else{
        return b;
    }
}
int minn(int a,int b){
    if(a<b){
        return a;
    }
    else{
        return b;
    }
}
typedef struct edge{
    int front;//起点
    int tail;//终点
    int num;//权值
}edge;//边的结构体
void init_edge(edge* e){
    scanf("%d %d %d",&e->front,&e->tail,&e->num);
}
void paixu_num(edge e[],int n){
    for(int i=1;i<n;i++){
        for(int j=0;j<n-i;j++){
            if(e[j].num>e[j+1].num){
                edge temp;
                temp=e[j];
                e[j]=e[j+1];
                e[j+1]=temp;
            }
        }
    }
}
int kruscal(edge e[],int n,int m){
    int sum=0;
    int cnt=0;//有效的边数
    for(int i=0;i<n;i++){
        biaohao[i]=i;
    }//将顶点的标号初始化,第i个顶点的标号初始化为i。
    for(int i=0;i<m;i++){
        int p1=e[i].front;//检索到的边的起点
        int p2=e[i].tail;//检索到的边的终点
        if(biaohao[p1]!=biaohao[p2]&&cnt!=n-1){//当起点和终点的标号不相等 即起点和终点是未连通的状态
            sum+=e[i].num;                    //且边数未达到要求时 将该边加入到图中
            cnt++;
            int max=maxx(biaohao[p1],biaohao[p2]);//求起点和终点标号的最大值
            int min=minn(biaohao[p1],biaohao[p2]);//求起点和终点标号的最小值
            for(int j=0;j<n;j++){
                if(biaohao[j]==max){
                    biaohao[j]=min;
                }
            }//将当前边的起点和终点中的最小标号赋给最大标号 并且 把所有 标号与 最大标号相同 的顶点的标号 改为最小标号
        }
    }
    if(cnt==n-1){
        return sum;
    }//判断边的个数是否符合要求
    else{
        return -1;
    }

}
int main()
{
    int n,m;
    scanf("%d",&n);//顶点
    scanf("%d",&m);//边
    int sum=0;
    edge e[m];//边数组
    for(int i=0;i<m;i++){
        init_edge(&e[i]);
    }//将边读入
    paixu_num(e,m);//将边按照权值排序 从小到大
    sum=kruscal(e,n,m);
    printf("%d",sum);
}

第一次写博客,欢迎大家批评指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值