动态规划 找零问题
动态规划的基本思想是将待求解问题分解成若干个子问题,先求解子问题,并将这些子问题的解保存起来,如果以后在求解较大子问题的时候需要用到这些子问题的解,就可以直接取出这些已经计算过的解而免去重复运算。保存子问题的解可以使用填表方式,例如保存在数组中。
用一个实际例子来体现动态规划的算法思想——硬币找零问题。
硬币找零问题描述:现存在一堆面值为 V1、V2、V3 … 个单位的硬币,问最少需要多少个硬币才能找出总值为 T 个单位的零钱?假设这一堆面值分别为 1、2、5、21、25 元,需要找出总值 T 为 63 元的零钱。
很明显,只要拿出 3 个 21 元的硬币就凑够了 63 元了。
基于上述动态规划的思想,我们可以从 1 元开始计算出最少需要几个硬币,然后再求 2 元、3元…每一次求得的结果都保存在一个数组中,以后需要用到时则直接取出即可。那么我们什么时候需要这些子问题的解呢?如何体现出由子问题的解得到较大问题的解呢?
其实,在我们从 1 元开始依次找零时,可以尝试一下当前要找零的面值(这里指 1 元)是否能够被分解成另一个已求解的面值的找零需要的硬币个数再加上这一堆硬币中的某个面值之和,如果这样分解之后最终的硬币数是最少的,那么问题就得到答案了。
单是上面的文字描述太抽象,先假定以下变量:
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 {
/**
* 硬币找零:动态规划算法
*
* @param values
* :保存每一种硬币的币值的数组
* @param valueKinds
* :币值不同的硬币种类数量,即coinValue[]数组的大小
* @param money
* :需要找零的面值
* @param coinsUsed
* :保存面值为i的纸币找零所需的最小硬币数
*/
public static void makeChange(int[] values, int valueKinds, int money,
int[] coinsUsed) {
coinsUsed[0] = 0;
// 对每一分钱都找零,即保存子问题的解以备用,即填表
for (int 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;
if (temp < minCoins) {
minCoins = temp;
}
}
}
// 保存最小硬币数
coinsUsed[cents] = minCoins;
System.out.println("面值为 " + (cents) + " 的最小硬币数 : "
+ coinsUsed[cents]);
}
}
public static void main(String[] args) {
// 硬币面值预先已经按降序排列
int[] coinValue = new int[] { 25, 21, 10, 5, 1 };
// 需要找零的面值
int money = 63;
// 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1
int[] coinsUsed = new int[money + 1];
makeChange(coinValue, coinValue.length, money, coinsUsed);
}
}
测试结果:
面值为 1 的最小硬币数 : 1 面值为 2 的最小硬币数 : 2 面值为 3 的最小硬币数 : 3 面值为 4 的最小硬币数 : 4 面值为 5 的最小硬币数 : 1 面值为 6 的最小硬币数 : 2 ... ... 面值为 60 的最小硬币数 : 3 面值为 61 的最小硬币数 : 4 面值为 62 的最小硬币数 : 4 面值为 63 的最小硬币数 : 3
#include <iostream> #include <algorithm> using namespace std; bool cmp(int a, int b) { return a < b; } /* * n 为需要找零的面值 * coinsUsed为记录各种面值的找零所需的最少的硬币数 * kinds为硬币的种类 * coins为各种硬币的币值,币值大小为依次增大的 */ /* *要求解面值为n的找零所需最少的硬币数,使用动态规划时,首先需要计算得所有面值 *小于 n的找零所需的最少的硬币数,并保存到数组coinsUsed中,当计算n时,可以把该 *问题分解为面值小于n的找零所需的最少的硬币数的组合,即分解为子问题,而这些子问题的 *的解已经在保存到了数组coinsUsed中,所有每个子问题只需计算一次 */ int CoinsChange(int n, int coinsUsed[], int kinds, int coins[]) { coinsUsed[0] = 0; //面值为0,其所需找零硬币数为0 for (int dollar = 1; dollar <= n; dollar++) { int minDollar = dollar / coins[0]; //面值为dollar的找零所需的最多硬币数为全部使用币值最小的硬币 for (int kind = 0; kind < kinds; kind++) { //依次使用各种币值的硬币,求得对应相应面值dollar所需最少硬币数 if (coins[kind] <= dollar) { //即该币值硬币可以对面值为dollar的找零时 int temp = coinsUsed[dollar-coins[kind]] + 1; //使用一个该币值的硬币并查表,即操作coinsUsed[dollar-coins[kind]]已经存在了的 if (temp < minDollar) minDollar = temp; //即如果该方案所得的硬币数量更少,则记录下来 } } coinsUsed[dollar] = minDollar; //该面值找零所需最少的硬币数,记录到表中,以便给面值更大的找零使用,此为自底向上的动态规划的做法 cout << "面值为 " << dollar << " 所需最少硬币数为:" << coinsUsed[dollar] << endl; } return coinsUsed[n]; //返回面值为n找零所需的最少硬币数 } int main() { int coins[5] = { 5, 1, 10, 21, 25 }; int kinds = 5; int n; sort(coins, coins + kinds-1, cmp); while (cin >> n) { int coinsUsed[n]; cout << CoinsChange(n, coinsUsed, kinds, coins) << endl; } return 0; }
转载:http://lovebeyond19831993.blog.163.com/blog/static/131811444201101901819833/