找硬币问题

考虑用最少的硬币数找n分钱的问题,假设每个硬币的值都是整数。

 

先证明问题具有最优子结构。假设对找n分前有最优解,而且最优解中使用了面值c的硬币,最优解使用了k个硬币。那么,这个最优解包含了对于找n-c分钱的最优解。显然,n-c分钱中使用了k-1个硬币。如果n-c分钱还有一个解使用了比k-1少的硬币,那么使用这个解可以为找n分钱产生小于k个硬币的解。与假设矛盾。

 

对于有些情况下,贪心算法可能不能产生最优解。比如硬币面值1,10,25.找30分钱,最优解是3*10,而贪心的情况下产生的解是1*5+25.

 

问题b可以作为一个结论:假设可换的硬币单位是c的幂,也就是c0,c1, ..., ck, 其中整数c>1, k>=1, 这种情况下贪心算法可以产生最优解。

问题的证明用到了引理:对于i=0,1,..., k, 设ai是找n分钱的最优解中面值c的数量。那么对i=0,1,...,k-1,有ai<c.

证明:假设ai>=c,对于0<=i<k. 则可以改进最优解通过增加一个面值ci+1的硬币和减少c个面值ci的硬币,这样找零钱的总数不变,但减少了c-1个硬币。于是,这个比最优解还要优化,矛盾。

 

然后证明问题具有贪心选择性质,即贪心选择具有最优解。这里证明不用贪心选择不能产生最优解。设j=max{0<=i<=k: ci<=n}.所以贪心解法会使用至少一个面值cj的硬币,考虑非贪心选择的解,不使用面值cj或更高的硬币。设非贪心的解使用ai数量的面值ci的硬币,对i=0,1, ... , j-1. 所以有a0c0+a1c1+ ... +aj-1cj-1=n.

因为n>=cj, 所以上式左边大于等于 cj. 现在假设这种非贪心选择的解是最优的,由引理ai<c,上式左边

a0c0+a1c1+ ... +aj-1cj-1  <= (c-1)c0+(c-1)c1+ ... +(c-1)cj-1 =(c-1)(1+c+...+cj-1)=(c-1)(cj-1)/(c-1)=(cj-1)<cj

与上面推出的左边大于等于cj矛盾。所以可以得到非贪心选择不能产生最优解。

贪心选择的话,问题的复杂度是O(k).K是硬币的个数。假设int a[k+1]是每个面值c0,c1, ..., ck使用的数量。

for(int i=k;i>=0;i--)

{

   a[i]=n/ci;

   n%=ci;

}

所以如果问题是这种情况,可以直接用贪心而且问题的解确实是最优的。

 

问题d更一般,设计O(nk)时间的算法,能够对任意k种不同单位组合的硬币集合进行找换,假设其中一种硬币单位是一分。

上面已经说明问题具有最优子结构,可以用动态规划求解最优的找零钱的解。

假设c[j]是找j分钱最少的硬币数。硬币的面值是d1,d2,...,dk.由于存在一分钱,所以对j分钱总是存在可以找的零钱。

可以写出递推的式子:

cj=    0,  if j<=0

cj=    1+ min{c[j-di]}, 1<=i<=k  if j>1

 

写了一个完整的代码,并有输出找钱方案的。测试例子就是上面给出的反例。对25,10,1分钱,找30分钱。

[cpp]  view plain copy
  1. #include <iostream>  
  2. using namespace std ;  
  3.   
  4. #define N  64  
  5.   
  6. void compute_change(int c[],int n ,int d[] ,int k,int a[])   
  7. {  
  8.     for(int j=1 ; j<=n ; j++)  
  9.     {  
  10.         c[j]=j;  
  11.         for(int i=1 ; i<=k ; i++)  
  12.         {  
  13.             if(j>=d[i] && (1+c[j-d[i]]) < c[j])  
  14.             {  
  15.                 c[j]=1+c[j-d[i]] ;  
  16.                 a[j]=d[i];  
  17.             }  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. void give_change(int a[],int j)  
  23. {  
  24.     if(j>0)  
  25.         cout<<"找零钱:"<<a[j]<<endl;  
  26.     give_change(a,j-a[j]);  
  27. }  
  28.   
  29. int main()  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值