本文主要介绍动态规划算法求解01背包问题。什么是01背包问题?什么是动态规划?动态规划怎么求解01背包问题?通过阅读本文能够解答上面的三个问题。
1、什么是01背包问题?
01背包问题是一种比较常见的问题,例如,你(或者你女盆友)有一个lv包包,出门的时候发现容量V固定,但是又要装口红,装防晒霜,装银行卡,装钥匙,装眼镜,装护手霜,装…,每种物品只能装一个,(是不是发现女人出门真麻烦?这也是女人最有智慧的地方,每次都能够快速的完成01背包问题的求解),需要装的这些东西呢只能选择装或者不装,每个物品的空间大小不一样,把这些物品的体积进行符号化,Ti (i=1,2…N)一共N个。但是每个物品在你心目中的重要程度不一样,比如,要出去购物,银行卡,手机是非常重要的,而口红可能重要性比较低,我们队每个物品Wi的权重符号化,设为 Wi. 将上面的符号汇总一下:
V: lv包包的最大容量
N: 物品的个数
Ti: 物品i的体积
Wi: 物品的权重
那么问题来了,在LV包包容量V固定且能够装下,每种物品只能取一次的情况下,需要装哪些物品,才能是的总权重最大化呢?这就是我们说的01背包问题。
2、什么是动态规划?
大家经常以为动态规划算法不像排序算法,递归算法等比较容易理解,其实,只要你记住了动态规划的核心器件,你就能够很容易的理解它并且用来解决问题。
动态规划,首先它是动态的,那么它当前的状态就要依赖前一次的状态,即状态转移,对前面的依赖,因此就需要一个状态表,来记录不同情况下的状态。上面表黑的两个是动态规划最重要的两个因素,一个是状态表,一个是状态转移,下面对他们进行数学表达:
状态: f[i][j] 选择前i个物品,体积为j时的最优方案,即所选物品的最大权重和。
状态转移:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j]) 前i个物品,体积为j时的最优值= 最大值(包含i个物品的最优值 , 前i-1个物品体积为j时的最优值)
2.1 动态规划状态
f[i][j]为状态,i代表当前从前i个物品中选择,背包的最大容量为j时,选择出来的物品的权重和的最大值。给大家举个栗子:
LV包包容量:10, f[3][7]是多少?
物品 | 物品体积 | 物品权重 |
---|---|---|
手机 | 5 | 10 |
银行卡 | 1 | 10 |
口红 | 2 | 1 |
车钥匙 | 3 | 10 |
护手霜 | 3 | 2 |
通过表格可以看出,容量为7时,选择手机和银行卡的最终的权重和最大是20,即f[3][6] = 20
2.2 状态转移方程
状态转移方程是动态规划最重要的一步,下面详细介绍一下状态转移方程的由来。
状态转移:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j])
从前i-1个物品中体积最大为j时最优解为f[i-1][j], 从i-1状态转移到i时,只需要考虑f[i][j]的最优解中包不包含第i个物品两种情况(大家仔细琢磨一下这一句)。
1)包含第i个物品:表示当前物品比前i个物品性价比高,能够替换掉一个 表达式:f[i-1][j-Ti]+Wi 表示从j中除去Ti的空间时的最优值然后加上当前第i个物品的权重。
2)第i个物品定价比低,无法替换掉i-1个物品,依然保持i-1状态的值,表达式: f[i-1][j]
因此,转移方程为:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j])
下面用事实说话,上栗子:
我们把i=3的所有状态计算出来。
f[3][1] | f[3][2] | f[3][3] | f[3][4] | f[3][5] | f[3][6] | f[3][7] | f[3][8] | f[3][9] | f[3][10] |
---|---|---|---|---|---|---|---|---|---|
10 | 10 | 11 | 11 | 10 | 20 | 20 | 21 | 21 | 21 |
1)f[i][j]包含第i个物品的情况
计算f[4][10],从信息表中可以得出,选择手机,银行卡,车钥匙 最终值为30,包含第4个物品车钥匙
我们用状态转移方程:
f[4][10] = max(f[3][10], f[3][10 - 3] + 10)
= max(21, f[3][7] + 10)
= max(21, 20 + 10) = 30
- f[i][j]不包含第j个物品的情况
f[3][7] ,从信息表中可以得出,选择手机,银行卡最终值为20,不包含第3个物品口红
我们用状态转移方程:
f[3][7] = max(f[2][7], f[2][7 - 2] + 1)
= max(20, f[2][5] + 1) // f[2][5] = 10 f[2][7] = 20
= max(20, 1) = 20
3、怎么求解01背包问题?
练习题:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
代码:
#include<stdio.h>
int f[1050][1050];
int v[1050];
int w[1050];
int max(int a, int b)
{
if (a > b) {
return a;
}
return b;
}
int main()
{
int n,m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &v[i], &w[i]);
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ) {
if (v[i] > j) { // 防止数组越界
f[i][j] = f[i - 1][j];
continue;
}
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
printf("%d\n", f[n][m]);
}
代码优化
通过上面的分析可以发现,f[i][j]的值仅仅与依赖i-1时的状态,因此,代码中无需使用二维的f[i][j]进行存储,使用一维的f[j]进行存储,减少程序的空间。
使用一维f,可能会有一个疑问:如果j从小到大的顺序依次更新f[j]的值,因此,在计算f[7] = max(f[7], f[7 - 3] + 10)时,括号内的f[4]已经是i时刻的值了,而非i-1时刻的值了,这个问题怎么解决呢?
聪明的朋友已经想到了:f[j] 的值在更新的时候,按照j从大到小的顺序依次更新,这样在计算f[7] = max(f[7], f[7 - 3] + 10)时,f[4]还未更新,还是i-1时刻的状态值,因此,这个问题就解决了,上代码。
#include<stdio.h>
int f[1050];
int v[1050];
int w[1050];
int max(int a, int b)
{
if (a > b) {
return a;
}
return b;
}
int main()
{
int n,m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &v[i], &w[i]);
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 1; j-- ) { //j从大到小的方向更新
if (v[i] > j) { // 防止数组越界
continue;
}
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
printf("%d\n", f[m]);
}