动态规划算法

  动态规划算法通常基于一个递推公式以及一个或多个初始状态,当前子问题的解由上一次子问题的解推出。

  在动态规划算法中有一个经典的例子就是硬币找零问题。

1、问题描述

  如果我们有面值为1元、3元、5元的硬币若干,如何用最少的硬币凑够11元?

2、思路分析

  基于动态规划的思想,我们可以从1元开始计算最少需要几个硬币,然后再求2元、3元、4元...

  首先,当i=0时,我们需要0个,即d(0)=0;

  当i=1时,只有面值为1元的硬币可用,d(1)=d(1-1)+1=1;

  当i=2时,仍然只有面值为1元的硬币可用,d(2)=d(2-1)+1=2;

  当i=3时,可用的硬币有1元和3元,如果我拿了1元的,我的目标就变成了凑够3-1=2元需要的最少硬币了,即d(3)=d(3-1)+1=3,另一个方案是我拿了一个3元的硬币,目标就是凑够3-3=0元需要的最少硬币数量,即d(3)=d(3-3)+1=1,所以综合两者方案的最小值d(3)=min{d(3-1)+1,d(3-3)+1}=1;

  当i=4时,可用的硬币有1元和3元,如果我拿了1元的,我的目标就变成了凑够4-1=3元需要的最少硬币了,即d(4)=d(4-1)+1=2,另一个方案是我拿了一个3元的硬币,目标就是凑够4-3=1元需要的最少硬币数量,即d(4)=d(4-3)+1=2,所以综合两者方案的最小值d(3)=min{d(4-1)+1,d(4-3)+1}=2;

  d(5)=1;

  d(6)=2;

  d(7)=3;

  d(8)=2;

  d(9)=3;

  d(10)=2;

  ...

  由上面可以退出公式,d(i)=min{d(i-vj)+1},其中i-vj>=0,vj表示第j个硬币的面值。

  现在,我们回到原题中,d(11)=min{d(11-vj)+1},其中vj可以取值为1,3,5。当vj=1时,d(11)=d(10)+1=3,当vj=3时,d(11)=d(8)+1=3,当vj=5时,d(11)=d(6)+1=3,所示d(11)=3。

3、代码示例

  首先定义以下变量:

  values[] : 保存每一种硬币的币值的数组
  valueKinds :币值不同的硬币种类数量,即values[]数组的大小
  money : 需要找零的面值
  coinsUsed[] : 保存面值为 i 的纸币找零所需的最小硬币数

  当求解总面值为 i 的找零最少硬币数 coinsUsed[ i ] 时,将其分解成求解 coinsUsed[ i – cents]和一个面值为 cents 元的硬币,由于 i – cents < i , 其解 coinsUsed[ i – cents] 已经存在,如果面值为 cents 的硬币满足题意,那么最终解 coinsUsed[ i ] 则等于 coinsUsed[ i – cents] 再加上 1(即面值为 cents)的这一个硬币。代码如下:

public class CoinsChange
{
    /**  
     * 硬币找零:动态规划算法  
     *   
     */ 
    public static void makeChange(int[] values, int valueKinds, int money, int[] coinsUsed)
    {  
        coinsUsed[0] = 0; 
        int cents ;
        // 对每一块钱都找零,即保存子问题的解以备用,即填表  
        for (cents = 1; cents <= money; cents++) 
        {  
            // 当用最小币值的硬币找零时,所需硬币数量最多  
            int minCoins = cents;  
            // 遍历每一种面值的硬币,看是否可作为找零的其中之一  
            for (int kind = 0; kind < valueKinds; kind++)
            {               
                // 若当前面值的硬币小于当前的cents则分解问题并查表  
                if (values[kind] <= cents)
                {  
                    int temp = coinsUsed[cents - values[kind]] + 1;  
                    // temp表示组合成cents需要的硬币数目 
                    if (temp < minCoins)
                    {  
                        minCoins = temp;  
                    }  
                }  
            }  
            // 保存最小硬币数  
            coinsUsed[cents] = minCoins;  
        }  
        System.out.println("面值为 " + (money) + " 的最小硬币数 : " + coinsUsed[cents-1]);  
    }  
    public static void main(String[] args)
    {
        int[] coinValue = new int[] { 1,3,5 };  
        // 需要找零的面值  
        int money = 11;  
        // 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1  
        int[] coinsUsed = new int[money + 1];  
        makeChange(coinValue, coinValue.length, money, coinsUsed); 
    }
}    

 

posted @ 2016-06-22 16:48  温布利往事  阅读(460)  评论(0编辑  收藏  举报