利用动态规划解决兑换问题

      问题是这样的:某个国家一共发行了a1,a2,a3,...,ak种不同面值的钞票,为了方便起见,假设a1,a2,a3,...ak依次增大。现在手上有的钱数为n,请问要如何把兑换成a1,a2,a3,...,ak这些钞票,使得所用的钞票的量为最少。这个问题看上去很简单,举一个例子,如果有1元,5元,10元3种钞票,而要兑换107元,于是就有a1=1,a2=5,a3=10,n=107。那么我们用面额最大的去除,那就是最大面额钞票的张数,比如说n/a3=107/10=10,亦即10元的10张;除过之后就有余数7,再用次大面额钞票去除,7/5=1,余数为2,所以5元钞票为1张;再把余数用第三大的面额去除,2/1=2,余数为0,于是一元的2张,没有剩下的钞票了,因此结果就是10元的7张,5元的1张,1元的2张。不过对有1元,5元,10元这个例子倒是正确,那么如果面额换为1元,3元,4元,要兑换10元的钞票呢?用上面的方法算一下,就是4元的2张,1元的2张,一共用了4张的钞票。但是若用2张3元的,1张4元的也能兑换出10元的,但却只用了3张钞票。所以看来平常的方法并不能解决“使所用钞票的量为最少”这个问题
     这个问题有很多的解法,这里只想介绍利用动态规划(Dynamic Programming)和回溯的技巧来解决。
     首先说说用到的数据结构,假设n是要兑换的钱数,k是能够提供的面额数目,如果要兑换100元,提供面额为1元,5元,7元的3种钞票于是有n=100,k=3。用一个数组base[]把面额先保存好,因此就有base[0]=1,base[1]=5,base[2]=7。但是这里我们假设一定提供面额为一元的钞票,否则可能有一些值兑换不出来。我们再用一个数组money[]存放兑换的钞票数,因此money[i]就是i元钱兑换出来的钞票数,由于money[0]表示0元兑换出来的钞票数,那么很自然就是0了,money[1]按照上面的假设一定是1。
     其次我们说说具体的算法,假设现在要兑换i元,先看i-base[0],i-base[1],... ,i-base[k-1]是什么。例如:i=10,而面额是{1,3,4},于是这几个值就是10-1=9,10-3=7,10-4=6,换句话说,如果已经兑换好了9元7元或6元的话,要兑换十元只不过是加一张钞票而已。因此,在i-base[j]的情况下,就是多一张j元。但是,如何知道兑换i-base [j]要多少张钞票呢?别忘了money[]啊,该钞票的张数就在money[i-base[j]]中。如果已经已经兑换出i-base[j]元,用了money[i-base[j]]张钞票,于是再多加一张base[j]元就可以兑换出i元了,这样兑换i元一共用了money[i-base[j]]+1张钞票。
      但是我们还没有解决题目所要求的钞票张数最少的问题,所以我们就要求出各个money[i-base[j]]+1的极小值来(j=1,2,...,k),再存在money[i]中,于是money[i]就是兑换i元的最少钞票张数了。要注意:因为i-base[j]一定小于i,所以在计算money[i]时,在i之前的值就一定要先算出来,这样money[i-base[j]]才会一个有意义的值。
      说了很多,下面看看一个具体的规划的表格,还是以n=10,base[]={1,3,4}为例:

                                    i                 money[i]               i-base[j]
                              ------------------------------------------------------------
                                   0                     0                        --, --  , --
                                   1                     1                        --, --  , --
                                   2                     2                    2-1, 2-3*, 2-4*
                                   3                     1                    3-1, 3-3 , 2-4*
                                   4                     1                    4-1, 4-3 , 4-4
                                   5                     2                    5-1, 5-3 , 5-4
                                   6                     2                    6-1, 6-3 , 6-4  
                                   7                     2                    7-1, 7-3 , 7-4
                                   8                     2                    8-1, 8-3 , 8-4
                                   9                     3                    9-1, 9-3 , 9-4
                                 10                     3                  10-1,10-3 ,10-4                       
                             -------------------------------------------------------------
     *表示小于零,不用理会极小值,只要根据表格进行回溯就能得出结果了,看表格发现用10-3和10-4进行回溯的结果是一样的,也就是3+3+4=10和4+4+3=10的道理了,C语言的代码如下:
 

  1. #include  <stdio.h></stdio.h>   
  2. #include  <stdlib.h></stdlib.h>   
  3. #define   MAXSIZE   100   
  4. #define   min(a,b)  ((a) <= (b) ? (a) : (b))   
  5.   
  6. int  main(void)  {   
  7.        int  money[MAXSIZE+1];   
  8.        int  base[] = { 1, 3, 4 };   
  9.        int  k = sizeof(base)/sizeof(int);   
  10.        int  n;   
  11.        int  i, j, MIN;   
  12.        char line[100];   
  13.        printf("\nMinimum Money Change Program");   
  14.        printf("\n----------------------------");   
  15.        printf("\n\nBase Values : ");   
  16.        for (i = 0; i < k; i++)   
  17.               printf("%d ", base[i]);   
  18.        printf("\n\nYour input please --> ");   
  19.        gets(line);   
  20.        n = atoi(line);   
  21.        money[0] = 0;             
  22.        money[1] = 1;       
  23.        for (i = 2; i <= n; i++)  {    
  24.                  MIN = n;              
  25.                  for (j = 0; j < k; j++)   
  26.                          if (i >= base[j])   
  27.                                   MIN = min(money[i-base[j]]+1, MIN);   
  28.                  money[i] = MIN;   
  29.        }   
  30.        printf("\n\nMinimum = %d", money[n]);   
  31.        getchar();   
  32. }     

     当然这个程序还有一个不足,那就是没有将每个面额的钞票的张数输出,还需要对程序进行一些改进,有兴趣一起研究一下吧...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值