综述
背包问题是动态规划中较为基础的一种问题,也是初学者学习动态规划时第一会学到的问题
背包问题只在有限的容量体积或称重下,放入背包有体积,重量,价值的物体,求在不超过背包限制下的价值最大。
根据不同的题目要求,可以把背包问题分为五大类——0/1背包,完全背包,多重背包,分组背包以及综合前四种的混合背包;其难度也是逐层递增
术语声明(后文符号所表示的含义)
物品的价值——v[ ]
物品的重量/体积——w[ ]
i ——第i件物品;
j ——总重不超过j;
f[ i ][ j ]——总重不超过j的前i件物品价值最大值 (二维数组)
f[ i ]——前i件物品价值最大 (一维数组优化)
0/1背包
0/1背包特点即是所有物品只能去一个或者是不取,即是取(1)/不取(0)
二维数组求解
假设第i阶段处理第i种物品,前i-1种物品已处理完毕(不影响第i次操作),只需要考虑第i-1次向第i次转移
状态表示 f[ i ][ j ]表示将前i件物品放入容量为j中获得的最大价值
第i中物品处理方式只有两种,即放或不放
①不放:放入背包的价值不增加,问题转化为求前i-1个物品放入容量为j的背包中的最大价值 f[ i ][ j ]=f[ i-1 ][ j ]
②放:问题转化为求前i-1种物品放入容量为j-w[ i ]的背包中的最大价值
f[ i ][ j ]=f[ i-1 ][ j-w[ i ] ]
j-w[ i ]的意义——先把第i件物品的重量排开,则前i-1件物品的重量+第i件物品的重量<背包容量j,否则将不符合这一步数组下标的意义
不知道读者有没有发现,上述讨论的情况仅是在背包容量的空间能装下第i件物品时才成立
(即j>=w[ i ])那如果已经无法装下第i件物品,则问题转化为求前i-1个物品放入容量为j的背包中的最大价值 即 f[ i ][ j ]=f[ i-1 ][ j ]
综上,将上述三种情况全部合起来看,就可以得出0/1背包用二维数组完成下的状态转移方程
① j<w(i) f[ i ][ j ]=f[ i-1 ][ j ] 。
② j>=w(i) f[ i ][ j ]=max(f[ i-1 ][ j ],f[ i-1 ][ j-w[ i ] )
代码实现
void dp01()//0/1背包
{
for(int i=1;i<=n;i++)
for(int j=1;j<=W;j++)
{
if(j<w[i])
//若第i种物品的重量加入后大于背包容量,则不放入
f[i][j]=f[i-1][j];
else if(j>=w[i])
{
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
}
}
cout<<"背包最大价值为 :"<<f[n][W];
//全部n种物品在背包容量为W时的价值最大值(即答案)
}
复杂度分析
本算法使用两层循环,时间复杂度为O(nW)。使用的是二维数组,空间复杂度为O(nW)。
一维数组优化
仔细思考就可以发现,每一次的f[ i ][ j ]只与f[ i-1 ][ j ]或者f[ i-1 ][ j-w[ i ] ]的值有关,即二维数组的上一排有关。且纵坐标(第二个括号内的数)都小于求的纵坐标(j>j-1 , j>j-w[ i ])所以可以用一维数组每次只保存上一排的数据(若不需要求最优解具体方案),对算法进行优化
根据二维数组的方式,归纳总结后就可以较为简单的得到一维数组解法的状态转移方程
f[ j ]=max(f[ j ],f[ j-w[ i ] ]+v[ i ])
void onezeropack()//0/1背包一维数组优化
{
for(int i=1;i<=n;i++)
for(int j=W;j>=w[i];j--)//倒推
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
以一道例题具体解释
问题描述
许多年前,在泰迪的家乡,有一个人被称为“骨头收藏家”。这个男人喜欢收集各种各样的骨头,比如狗狗,牛,还有他去了坟墓......
骨头收藏家有一个大容量的V袋,沿着他的收集之旅有很多骨头,显然,不同的骨骼具有不同的值和不同的体积,现在根据他的行程给出每个骨骼的值,你能计算出骨骼采集器可以得到的总值的最大值吗?输入
第一行包含整数T。
接下来是T个案例,每个案例有三行,第一行包含两个整数N,V,(N <= 1000,V <= 1000)表示骨骼的数量和他的包的体积。第二行包含表示每个骨骼值的N个整数。第三行包含表示每个骨骼体积的N个整数。输出
每行一个整数表示总值的最大值
样本输入
1
5 10
1 2 3 4 5
5 4 3 2 1样本输出
14
来源——HDU2602 骨头收藏家
不过多解释二维数组的解法,直接上代码
#include<bits/stdc++.h>
using namespace std;
int f[1001][1001],t,v[1001],w[1001],n,W;
void dp01();
int main()
{
cin>>t;
while(t--)
{
memset(f,0,sizeof(f));
memset(v,0,sizeof(v));
memset(w,0,sizeof(w));//注意多组数据要在每次都初始化
cin>>n>>W;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++) cin>>w[i];
dp01();
}
}
void dp01()//0/1背包
{
for(int i=1;i<=n;i++)
for(int j=0;j<=W;j++)//本题的坑——骨头体积可能为零!
{
if(j<w[i])
//若第i种物品的重量加入后大于背包容量,则不放入
f[i][j]=f[i-1][j];
else if(j>=w[i])
{
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
}
}
cout<<f[n][W];
//全部n种物品在背包容量为W时的价值最大值(即答案)
}
下面主要是通过这个题详细介绍一下如何用一维数组优化算法求解
样例输入数据整理
物品 | 1 | 2 | 3 | 4 | 5 |
价值(v) | 1 | 2 | 3 | 4 | 5 |
重量(w) | 5 | 4 | 3 | 2 | 1 |
先对样例进行模拟
①当i=1 j∈[ 5,10 ] w[ i ]=w[ 1 ]=5 v[ i ]=v[ 1 ]=1
j | F[ j ] | |
0 | 0 | \ |
1 | 0 | \ |
2 | 0 | \ |
3 | 0 |