数据结构与算法(C语言版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.4 贪心法

2.4.1 贪心法的设计思想

贪心法(greedymethod)是把一个复杂问题分解为一系列较为简单的局部最优选择,每一步选择都是对当前解的一个扩展,直到获得问题的完整解。正如其名字一样,贪心法在解决问题的策略上目光短浅,只根据当前已有的信息做出选择,而且一旦做出了选择,不管将来有什么结果,这个选择都不会改变。例如用贪心法求解付款问题:假设有面值为{5元、2元、1元、5角、2角、1角}的货币若干张,需要付款4元6角现金,如何付款才能使付出货币的数量最少?付款问题的贪心选择策略是,在不超过应付款金额的条件下,选择面值最大的货币。首先选出1张面值不超过4元6角的最大面值的货币,即2元;再选出1张面值不超过2元6角的最大面值的货币,即2元;再选出1张面值不超过6角的最大面值的货币,即5角;再选出1张面值不超过1角的最大面值的货币,即1角。总共付出4张货币。

显然,贪心法的关键是设计合理的贪心选择策略。在本书中,哈夫曼算法、Prim算法、Kruskal算法、Dijkstra算法等都是贪心法的应用实例。

2.4.2 算法设计实例——埃及分数

【问题】 埃及同中国一样,也是世界文明古国之一。古埃及人只用分子为1的分数,在表示一个真分数时,将其分解为若干个埃及分数之和,例如:7/8表示为1/2+1/3+1/24。设计程序把一个真分数表示为最少的埃及分数之和的形式。

【想法】 一个真分数的埃及分数表示不是唯一的,例如:7/8又可以表示为1/8+1/8+1/8+1/8+1/8+1/8+1/8。显然,把一个真分数表示为最少的埃及分数之和的形式,其贪心策略是选择真分数包含的最大埃及分数,以7/8为例,7/8>1/2,则1/2是第一次贪心选择的结果;7/8-1/2=3/8>1/3,则1/3是第二次贪心选择的结果;7/8-1/2-1/3=1/24,则1/24是第三次贪心选择的结果,即7/8=1/2+1/3+1/24。

接下来的问题是:如何找到真分数包含的最大埃及分数?设真分数为A/B,B除以A的整数部分为C,余数为D,则有下式成立:

B=AC+D

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

A/B>1/(C+1)

1/(C+1)即为真分数A/B包含的最大埃及分数。设E=C+1,由于

A/B-1/E=(AE-B)/(BE)

则真分数减去最大埃及分数后,得到真分数(AE-B)/(BE),该真分数可能存在公因子,需要化简,可以将分子和分母同时除以最大公约数。

【算法】 设函数EgyptFraction实现埃及分数问题,其算法描述如下 :

算法:EgyptFraction(A,B)

输入:真分数的分子A和分母B

输出:最少的埃及分数之和

1.E=B/A+1;

2.输出1/E;

3.A=A*E-B;B=B*E;

4.求A和B的最大公约数R,如果R不为1,则将A和B同时除以R;

5.如果A等于1,则输出1/B,算法结束;否则转步骤1重复执行;

【程序】 主函数首先接收从键盘输入的分子A和分母B,然后调用函数EgyptFraction将该真分数表示为埃及分数之和,在表示过程中需要调用函数CommonFactor求A和B的最大公约数并对A/B进行化简。程序如下:

            #include<stdio.h>                  //使用库函数printf和scanf
            voidEgyptFraction(intA,intB);    //函数声明,表示埃及分数
            intCommonFactor(intm,intn);      //函数声明,求最大公约数
                                                 //空行,以下是主函数
            intmain()
            {
              intA,B;
              printf("请输入真分数的分子:");//输出提示信息
              scanf("%d",&A);
              printf("请输入真分数的分母:");//输出提示信息
              scanf("%d",&B);
              EgyptFraction(A,B);            //函数调用,表示真分数A/B
              return0;                          //将0返回操作系统,表明程序正常结束
            }
                                                 //空行,以下是其他函数定义
            voidEgyptFraction(intA,intB)
            {                                    //函数定义,把真分数表示为最少的埃及分数之和
              intE,R;
              printf("%d/%d=",A,B);       //输出真分数A/B
              do
              {
                  E=B/A+1;                      //求真分数A/B包含的最大埃及分数
                  printf("1/%d",E);        //输出1/E
                  printf("+");
                  A=A*E-B;                      //以下两条语句计算A/B-1/E
                  B=B*E;
                  R=CommonFactor(B,A);       //函数调用,求A和B的最大公约数
                  if(R>1)                     //最大公约数大于1,即A/B可以化简
                  {
                    A=A/R;B=B/R;               //将A/B化简
                  }
              }while(A>1);                   //当A/B不是埃及分数时执行循环
              printf("1/%d\n",B);          //输出最后一个埃及分数1/B
              return;                           //结束函数EgyptFraction的执行
            }
            intCommonFactor(intm,intn)        //函数定义,求m和n的最大公约数
            {
              intr=m% n;
              while(r!=0)                     //当余数不为0时执行循环
              {
                  m=n;
                  n=r;
                  r=m% n;
              }
              returnn;                          //返回最大公约数n
            }