动态规划:最少硬币找零问题、01背包问题、完全背包问题
来源:互联网 发布:mplayerx for mac下载 编辑:程序博客网 时间:2024/05/31 18:34
题目一:01背包问题
一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],现在往背包里面装东西,怎么装能使背包的内物品价值最大?
题目二:完全背包问题
一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],每个物品都有无限多件,现在往背包里面装东西,怎么装能使背包的内物品价值最大?
题目三:最少硬币找零问题
给予不同面值的硬币若干种种(每种硬币个数无限多),如何用若干种硬币组合为某种面额的钱,使硬币的的个数最少?
在现实生活中,我们往往使用的是贪心算法,比如找零时需要13元,我们先找10元,再找2元,再找1元。如果我们的零钱可用的有1、2、5、9、10。我们找零18元时,贪心算法的策略是:10+5+2+1,四种,但是明明可以用两个9元的啊。这种问题一般使用动态规划来解决。
一、首先来看01背包问题
用一个数组f[i][j]表示,在只有i个物品,容量为j的情况下背包问题的最优解。第i个物品可以选择放进背包或者不放进背包(这也就是0和1),假设放进背包(前提是放得下),那么f[i][j]=f[i-1][j-weight[i]+value[i];如果不放进背包,那么f[i][j]=f[i-1][j]。
这就得出了状态转移方程:
f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]+value[i])实现代码
#include<iostream> using namespace std; #define V 1500 unsigned int f[10][V];//全局变量,自动初始化为0 unsigned int weight[10]; unsigned int value[10]; #define max(x,y) (x)>(y)?(x):(y) int main() { int N,M; cin>>N;//物品个数 cin>>M;//背包容量 for (int i=1;i<=N; i++) { cin>>weight[i]>>value[i]; } for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) { if (weight[i]<=j) { f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]); } else f[i][j]=f[i-1][j]; } cout<<f[N][M]<<endl;//输出最优解 }在hihocoder上面还讲到可以进一步优化内存使用。上面计算f[i][j]可以看出,在计算f[i][j]时只使用了f[i-1][0……j],i-1没有变化,j是从0一直递增,因此可以用一个一维数组存储i-1时求得j对应的每个f[j];然后求i时,利用i-1时的数组f[j]递推求得到i时f[j],数组复用同一个。再进一步思考,为了复用数组时不对数据产生污染,计算f[j]时应该从后往前算,即 j=M......1
for i=1……N
for j=M……1
f[j]=max(f[j],f[j-weight[i]+value[i])
实现代码:
#include<iostream> using namespace std; #define V 1500 unsigned int f[V];//全局变量,自动初始化为0 unsigned int weight[10]; unsigned int value[10]; #define max(x,y) (x)>(y)?(x):(y) int main() { int N,M; cin>>N;//物品个数 cin>>M;//背包容量 for (int i=1;i<=N; i++) { cin>>weight[i]>>value[i]; } for (int i=1; i<=N; i++) for (int j=M; j>=1; j--) { if (weight[i]<=j) { f[j]=max(f[j],f[j-weight[i]]+value[i]); } } cout<<f[M]<<endl;//输出最优解 }
其实这个两个问题非常相似,都是物品数目无限多,一个是不超过某个重量值W求最大value,一个是要获得某个value,求最小重量(每个硬币可以看成是重量为1的物品)。
解法1:
(1)对于完全背包问题状态转移方程:
f[i][j]=max(f[i-1][j-k*weight[i]+k*value[i]),其中0<=k<=j/weight[i]
可以理解为:j为背包可以容纳的重量,有i种物品时,向背包里添加第i种物品,第i种物品可以添加的个数范围是 0<=k<=j/weight[i]
(2)对于硬币找零问题状态转移方程:
f[i][j]=min(f[i-1][j-k*value[i]+k),其中0<=k<=j/value[i]可以理解为:j为需要找零多少元,有i种硬币,找零时选取第i种硬币,第i种硬币可以选取的枚数是 0<=k<=j/value[i]
注意:其实上面这种解法看来貌似没什么问题,但是上面的递归公式有冗余计算,例如下面两个式子:
f[i][j]=max{ f[i−1,j− value(i)∗k] + value(i)∗k }, 0≤ k ≤ x/need(i)
f[i][j−value(i)]=max{ f[i−1][j− value(i)∗k] + value(i)∗k }, 1≤ k ≤ x/value(i)
在计算上面第一个式子时,又把第二个式子中大部分重新计算了一遍。 所以解法1并不高效,一般不会用。
解法2:
(1)对于完全背包问题状态转移方程:
f (j) = max{ f(j - weight[i]) + value[i], i = 0......N }可以理解为:j为背包可以容纳的重量,有N种物品,对于每种物品假设至少包含一个,至于到底包含多少个我们并不关心。
(2)对于最少硬币找零问题状态转移方程:
f (j) = min{ f(j - coin[i]) + 1, i = 0......N }
可以理解为:j为需要找零多少元,有N种硬币,对于每种硬币,我们可以依次假设f(i)中至少包含一个coin[j] (j=0, 1......N) ,然后得到所需的最少硬币是f(j- coin[i]) + 1,最后再从这N次假设中选出最小的就是f(i)。
有人可能会有疑问,为什么只是假设存在一块硬币coin[j],存在k块硬币难道不用考虑吗?假如f(i)真的包含多个coin[j],我们只取一个coin[j],那么剩下的几个coin[j]的最优组合肯定已经包含在 f(i - coin[j]) 里面了,我们根本不用关心它们。
解法3: 上面的状态转移方程比较难以理解(不常用),下面换一种更通用的状态方程
(1)对于完全背包问题状态转移方程:
f[ i ] [ j ] = max( f[i-1][j], f[ i ][ j- weight[i] ] + value[i] ) ,注意后面是f[i, j-weight[i]],i 没有减1可以理解为:j为背包可以容纳的重量,有i种物品时,对于第i种物品,要么取或者不取,至于取多少个我们并不关心。
(2)对于硬币找零问题状态转移方程:
f[i][j]=min( f[i-1][ j ], f [i ] [ j - value[i] ] + 1) ,注意后面是f[i, j-value[i]],i 没有减1可以理解为:j为需要找零多少元,有i种硬币,找零时对于第i种硬币,我们只考虑取或者不取,至于取多少个我们并不关心!
两种边界情况说明一下:
(1)f[0][j]=Integer.MAXVALUE ,因为 对金额为 j 的钱找零,但是可以的硬币面值种类为0,这显然是无法做到的。其实这是一个”未定义“的状态。它之所以初始为Integer.MAXVALUE
(2)f[i][0]=0,因为,对金额为0的钱找零,可用来找零的硬币种类有 i 种,金额为0怎么找啊,故设置为0。
/* * * @param coinsValues 可用来找零的硬币 coinsValues.length是硬币的种类 * @param n 待找的零钱 * @return 最少硬币数目 */ public static int charge(int[] coinsValues, int n){ int[][] c = new int[coinsValues.length + 1][n + 1]; // 初始化边界条件 for(int i = 0; i <= coinsValues.length; i++) { c[i][0] = 0; } for(int i = 0; i <= n; i++){ c[0][i] = Integer.MAX_VALUE; } for(int i = 1; i<=coinsValues.length; i++){ //i表示参加找零的硬币的种类1~i种硬币 for(int j = 1; j <= n; j++){//j表示需要找零的钱数 if(j < coinsValues[i-1]){ c[i][j] = c[i - 1][j]; continue; } //每个问题的选择数目---选其中较小的 if(c[i - 1][j] < (c[i][j - coinsValues[i-1]] +1)) { c[i][j] = c[i - 1][j]; } else { c[i][j] = c[i][j - coinsValues[i-1]] +1; } } } return c[coinsValues.length][n]; }
- 动态规划:最少硬币找零问题、01背包问题、完全背包问题
- 背包问题 - 硬币找零
- 最少硬币找零问题-动态规划
- 最少硬币找零问题-动态规划
- 动态规划之最少硬币找零问题
- 硬币找零问题(完全背包)
- 背包问题-背包01-硬币找零
- 动态规划01背包问题(最少硬币问题、开心的小明,01背包问题)
- 完全背包---找零问题
- 动态规划-----背包问题-----01背包,完全背包,多重背包
- JAVA动态规划(一)--最少硬币找零问题
- 硬币找零问题 - 动态规划
- 最少硬币找零问题
- 最少找零问题与完全背包模型的一点思考
- [背包问题] 01背包、完全背包、整数找零
- 完全背包问题 动态规划
- 动态规划-完全背包问题
- 完全背包问题-动态规划
- thinkphp中Auth权限类的使用
- Spark on YARN cluster作业运行全过程分析
- 苹果手机如何制作铃声
- (三)Spring详解——创建bean实例
- 一个4tab选项卡页面(一个json)
- 动态规划:最少硬币找零问题、01背包问题、完全背包问题
- (2).Mybatis配置文件优化
- Android N(7.0)CTS测试注意事项
- ListView滚动到顶部和底部的判断
- IntelliJ IDEA 常用快捷键列表及技巧大全
- 【计算机视觉】人脸识别之人脸对齐(一)--定义及作用
- 从0开始学习 GitHub 系列之「03.Git 速成」
- select下拉菜单,无限加载切换菜单,实现自动加载子节点。
- 分组查询最大/最小值sql