算法设计与分析——第五篇,贪婪法

写在前面的话——

贪婪法我觉得就比较好懂了,不过还是觉得书上写的就不是人能懂的东西,什么局部最优整体最优,真的要通过实例来理解,其实就是,如果一个问题可以分为好几个小问题,我理解为分好几个步骤,或者是好几个部分,比如一串数,可以分为好几串少一点的数,所谓局部最优就是这群少一点的数中找到最大的,再在这些最大的中找更大的,就是所谓的合成一个大问题,整体最优,也就是每一步都找最优的,直到最后这个数,就会是整体最优的,其实也觉得没有说的很明白,我是通过做题来领会的


第五篇——

貌似今天的题,题目是有图表的,我在word上是画出来的,不知道复制过来能不能看懂,第二题,无所谓了,感觉第一题还是 比较依赖数学模型,数学不好的人,没法做吧,怎么来找最大的分数真是想死,所以数学很关键!第二题就是比较类似实际运用的题之类的,我觉得这个题也没法用分治法来做吧,不过我觉得最大问题在于怎么理解题意,理解了之后,其实很快就写出来了,果然比较简单,因为写这个的时候,已经学过动态规划了,觉得贪婪法就是动态规划的一种简单方式,至少在这种题的写法上



1、设计一个算法,把一个真分数表示为埃及分数之和的形式。所谓埃及分数,是指分子为1的分数。

 

算法设计:

用贪婪法找到一个用最少埃及分数的表示一个真分数的表达式,基本思想是,逐步选择分数所包含的最大埃及分数,这些埃及分数之和就是一个问题的解。因为高级程序设计语言不能支持分数的运算,所以需要建立如下的数学模型

数学模型:

将一个真分数F表示为A/B;做B整除A的运算,商为D,余数为K(0<K<A),它们之间的关系及导出关系如下:

B=A*D+K

B/A=D+K/A<D+1

A/B>1/(D+1)

记C+D+1,这样,就找到了分数F所包含的“最大的”埃及分数,就是1/C。进一步计算:

A/B-1/C=(A*C-B)/(B*C)

也就是说继续要解决的是有关分子为A=A*C-B,分母为B=B*C的子问题。

根据以上数学模型,可以设计算法,需要一个循环,退出条件为正好得到新的分数是一个埃及分数,即求出所有的埃及分数,循环体的内容即为数学模型的内容,算出最大包含的最大的埃及分数,并且得到新的分数,判断这个分数是否为埃及分数,为否则继续循环。

 

源码如下:

 

#include <stdio.h>
 
int main()
{
    int a,b,c;
    printf("输入一个真分数:");
    scanf("%d/%d",&a,&b);//a为分数分子,b为分数分母
 
    while(a >= b) {
        printf("输入错误,重新输入:");
        scanf("%d/%d",&a,&b);
    }
 
    if(0 == b % a) {//当a==1的情况也满足b%a==0
        printf("%d/%d = 1/%d\n",a,b,b/a);
        return 0;
    }
 
    printf("%d/%d = ",a,b);
    while(a != 1) {
        c = b /a + 1;
        a = a *c - b;
        b = b *c;
        printf("1/%d + ",c);
        if(0 == b % a) {
            printf("1/%d\n",b/a);
            break;
        }
    }
 
    return 0;
}



算法分析:

需要考虑到贪婪法的算法思维,优先找到局部最优解,即找到每一步都是找最大的埃及分数,同时需要推导出相应的数学模型才能方便的写出完整代码。

 

 

 

2、某旅游区的街道成网格状,其中东西向的街道都是旅游街,南北向的街道都是林荫道。由于游客众多,旅游街被规定为单行道。游客再旅游街上只能从西向东走,在林荫道上既可以由南向北走也可以从北向南走。

阿隆想到这个旅游区游玩。他的好友阿福给了他一些建议,用分值表示所有旅游街相邻两个路口之间的道路浏览的必要程度,分值从-100到100的整数,所有林荫道不打分。所有分值不可能全是负值。

阿隆可以从任一路口开始浏览,在任一路口结束浏览。请写一个算法,帮助阿隆寻找一条最佳的浏览路线,使得这条路线所有的分值总和最大。

 

+       -50   +       -47   +       36     +       -30   +       -23   +

|                 |                 |                 |                 |                 |

西 +       17     +       -19   +       -34   +       -43   +       -8      +                东

|                 |                 |                 |                 |                 |

+       -42   +       -3      +       -43   +       34     +       -45   +

 

算法设计:

可以先整理一下分值,输入到一个二维数组中,为

a[3][5]={   -50 -47 36 -30 -23

                   17 -19 -34 -43 -8

                  -42 -3 -43 34 -45      }

然后使用贪婪法,求出每一列的最优解,合成即为整体最优解,但是考虑到,题目并没有要求必须从0开始到最后结束,所以还应该找出每一列的最优解之后还需要求最大子段和,找出最大的分值和,才算得出最终浏览方案,浏览方案以需要浏览的路口为1,不需要浏览的为0标注。

 

源代码如下:


#include <stdio.h>
 
#define N 3
#define M 5
 
int maxSum(int a[M][3],int left,int right,int *begin,int *over);
 
int main()
{
       int road[N][M];
       int i,j;
       printf("按顺序输入所有路线分值:\n");
       for(i = 0; i < N; i++) {
              for(j = 0; j < M; j++) {
                     scanf("%d",&road[i][j]);
              }
       }
       printf("正在运算\n");
 
       /*使用贪婪法,先找出每列的最优解,然后合并为整体的最优解
       但是根据题意,可以在任一路口开始任一路口结束,所以可以在
       找出的最优解中找到找出最大子段和,即可完成题目*/
       int a[M][3] = {0};
       for(j = 0; j < M; j++) {
              for(i = 0; i < N; i++) {
                     if(0 == i || a[j][0] < road[i][j]) {//使用枚举法找出最大值,因为位数不是很多,否则可以考虑使用分治法求最大值
                            a[j][0]= road[i][j];
                            a[j][1]= i;
                            a[j][2]= j;//记录当前最大值的值和数组下标
                     }
              }
       }
 
       /*找出最大子段和*/
 
       int beginPoint,overPoint;
       maxSum(a,0,M-1,&beginPoint,&overPoint);
 
       int bestRoad[N][M] = {0};
       for(i = beginPoint; i <= overPoint; i++) {
              bestRoad[a[i][1]][a[i][2]]= 1;
       }
 
       printf("最佳路线是:\n");
       for(i = 0; i < N; i++) {
              for(j = 0; j < M; j++) {
                     printf("%d",bestRoad[i][j]);
              }
              printf("\n");
       }
 
       return 0;
}
 
int maxSum(int a[M][3],int left,int right,int *begin,int *over)
{
       int mid,i,lsum,rsum,sum1,sum2,lefts,rights;
       if(left == right) {
              return a[left][0];
       }else {
              mid= (left + right)/2;
              lsum= maxSum(a,left,mid,begin,over);
              rsum= maxSum(a,mid+1,right,begin,over);
 
              sum1= 0;
              lefts= 0;
              for(i = mid; i >= left; i--) {
                     lefts= lefts+a[i][0];
                     if(lefts > sum1) {
                            sum1= lefts;
                            *begin= i;
                     }
              }
 
              sum2= 0;
              rights= 0;
              for(i = mid+1; i <= right; i++) {
                     rights= rights+a[i][0];
                     if(rights > sum2) {
                            sum2= rights;
                            *over= i;
                     }
              }
              if(sum1 + sum2 < lsum && rsum < lsum) {
                     *begin= left;
                     *over= mid;
                     return lsum;
              }
              if(sum1 + sum2 < rsum) {
                     *begin= mid+1;
                     *over= right;
                     return rsum;
              }
 
              return sum1 + sum2;
       }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值