0-1背包问题
给定n个物品和一个背包。物品i的重量为wi,价值为vi,背包容量为c。选择装入背包中的物品,使得装入背包的物品的价值最大。每种物品i只有两种选择,装入或者不装入,既不能装入多次,也不能只装入一部分。
测试用例:
n = 4,c = 9,w[4] = {1, 2, 3, 5},v[4] = {4, 7, 9, 10}
x = [1, 0, 1, 1]
算法总结
动态规划算法与回溯算法解决0-1背包问题的区别:
动态规划算法是我不知道要不要把这个物品装入背包,所以我选择了列表的方式,通过查表和当前的判断决定要不要装入。
回溯算法是利用树的结构,不同分支方向代表不同的选择。在构建树的过程中,利用约束函数(保证问题在合理性范围之内)和限界函数(对不可能出现最优解的分支放弃遍历)来快速的找到最优解。
1.动态规划算法
问题分析:
0-1背包问题区别于背包问题。背包问题中物品可以装入一部分,故使用贪心算法,每次装入单位重量价值最高的即可。0-1背包问题面对一个物品,有装与不装2个选择。
其特性是,无论当前选择装,或者当前选择不装,都不能确定以后是否会有更优的取物方式。也就是说,当物品个数从少变到多,选取的价值最大的物品可能改变。这会导致,当可选择的物品个数改变时,我们只能再次判断曾判断过的物品。
即,采用一个个判断物品装入是否能够获得最大价值时,分解得到的子问题不是相互独立的,有些物品被重复判断了多次。
因此,如果能将物品i是否可以装入背包,也就是判断后的结果保存下来,就可以避免大量重复计算,为以后的计算节省很多时间。这也是动态规划算法的基本思想。可以看出,动态规划是以自底向上的方式计算最优值。
自底向上,想法不错。但要注意,以上仅得到了解决问题的基本思想。到底如何基于物品数较少的情况推导出物品数较多的情况目前没有具体的方式。
通常来说,面对一个物品,有3种情况:
- 背包剩余容量不足,无法装入。此时背包重量与价值都和选择上一个物品时相同。
- 背包剩余容量足够,选择装入。此时背包重量增加wi,价值在选择上一个物品后的基础上加vi。
- 背包剩余容量足够,选择不装入。此时背包重量与价值都和选择上一个物品时相同。
不装入当前物品,价值一定不会增加,但可能为下面更划算的物品腾出空间。装入当前物品,价值一定会增加,但不一定会到达当前最优价值。所以我们需要在物品装与不装之间选择最优的一个。
若m(i, j)表示当前背包容量为j,选择物品为n, n-1, n-2, …, i+1, i 装入背包产生的价值,则 m ( i , j ) = m a x ( m ( i + 1 , j ) , m ( i + 1 , j − w i ) + v i ) m(i,j)=max( m(i+1, j), m(i+1,j-wi)+vi ) m(i,j)=max(m(i+1,j),m(i+1,j−wi)+vi)。其中m(i+1,j)表示不装i的价值,m(i+1,j-wi)+vi表示装了第i个商品,背包容量减少wi, 但价值增加了vi。
c++实现:
自底向上决定了m数组数值变更时,会依赖已变更的值。故先初始化第一行,便于此为依据。
对于每一个物品都可以分为两部分“填表”:①装不进,即当前背包容量小于物品重量②能装进,判断两种情况取最优值
#include<iostream>
using namespace std;
#define N 10
int m[N][N], x[N];
void Knapsack(int v[], int w[], int c, int n){
//从后向前装
int jMax = w[n] - 1 < c ? w[n] - 1 : c;//背包剩余容量上限
for(int j = 0; j <= jMax; ++j){
m[n