动态规划算法通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想都是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表(备用表)来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
能采用动态规划求解的问题的一般要具有3个性质:
(1)最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2)无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
动态规划算法解决换零钱问题。
现存在一堆面值为 1,2,5,11,20,50 面值的硬币,问最少需要多少个硬币才能找出总值为 N个单位的零钱
动态规划算法思路:
记d{n}={}表示兑换面值为n的最优解组合为{x1,x2,x3…}
从子问题出发,取0元只有一种方法为{0},即d(0)={0},取1元(0+1)元,最优解为1元+0元,即d(0)+1={0,1}={1};
取2元有两种方式,即d(0)+2={2}或d(1)+1={1,1},易知最化解为{2}……
以此累推,兑换面值为n的最优解为d(n)=max{d(n-i)+i},其中(i<=n)
当硬币数量无限时,程序代码如下:
public static void changeCoins(int[] coins,int money) {
/*保存面值为i的纸币找零所需的最小硬币数*/
int [] coinsUsed = new int [money+1];
/*硬币种类数量*/
int valueKinds = coins.length;
/*0元的最优解*/
coinsUsed[0] = 0;
Map<Integer,HashMap<Integer,Integer>> coinChangeMap = new HashMap<Integer,HashMap<Integer,Integer>>();
/*从1 - money先求子问题的最优解*/
for(int cents = 1;cents<=money;cents++) {
/*当用最小币值的硬币找零时,所需硬币数量最多*/
int minCount = cents;
/*保存各个面值的具体找零方案*/
HashMap<Integer,Integer> minCoinMap = new HashMap<Integer,Integer>();
/*遍历每一种面值的硬币,看是否可作为找零的其中之一*/
for(int kind =0;kind<valueKinds;kind++) {
/*当前面值*/
int coinVal = coins[kind];
int oppCoinVal = cents - coinVal;
/*若当前面值的硬币小于当前的cents则分解问题并查表*/
if(coinVal <=cents) {
int tempCount = coinsUsed[oppCoinVal]+1;
if(tempCount<=minCount) {
/*子问题的最优解*/
HashMap<Integer,Integer> subMap = coinChangeMap.get(oppCoinVal);
HashMap<Integer,Integer> tmpMap = new HashMap<Integer,Integer>();
if(subMap!=null) {
/*取到了子问题的最优解*/
tmpMap.putAll(subMap);
}
/*子问题的最优解+剩下面值 ->整个问题的最优解决*/
if(tmpMap.containsKey(coinVal)) {
tmpMap.put(coinVal, subMap.get(coinVal)+1);
}else {
tmpMap.put(coinVal, 1);
}
minCount = tempCount;
minCoinMap = tmpMap;
}
}
}
// 保存最小硬币数
coinsUsed[cents] = minCount;
coinChangeMap.put(cents, minCoinMap);
getArrayItem(coinsUsed);
System.out.println("面值为 " + (cents) + " 的最小硬币数 : " + minCount+",货币为"+ minCoinMap);
}
}
public static void main(String[] args) {
// 硬币面值预先已经按降序排列
int[] coinValue = new int[] { 50, 20, 11, 5, 2,1 };
// 需要找零的面值
int money = 23;
// 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1
changeCoins(coinValue, money);
}
运行截图:
本文介绍了动态规划算法的概念和特点,强调了最优化原理、无后效性和有重叠子问题这3个关键性质。通过一个找零钱问题的例子,详细解释了动态规划算法的思路,并给出了当硬币数量无限时的解决方案。动态规划方法能够避免重复计算,有效地求解最优解。
1291

被折叠的 条评论
为什么被折叠?



