动态规划——背包问题(一)01背包、完全背包
本次博客,我将记录自己对动态规划中背包问题的理解
01背包问题
首先问题如下:
有N件物品和一个容量为V的背包。第i件物品的质量是weight[i],价值是value[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
具体思路:
对于第n件物品,如果它装不进当前的背包,那就跳过,如果当前背包装得下,那么就有两条路:装,或者不装;
装:总价值=前n-1件物品的总价值+第n件物品的价值
不装:总价值=前n-1件物品的总价值
接着,我们比较以上两种思路下物品的价值,并取总价值最高的思路
不妨举例,有如下4件物品
编号:1 2 3 4
质量:2 3 4 5
价值:3 4 5 6
背包总容量:8
对此,我列出表格
因此,列出状态转移方程:
f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i])
其中,f[i][v]表示选取前i件物品放入一个容量为v的背包可以获得的最大价值
此处我的理解是:对于第i件物品,max()中左值为不装该物品背包的总价值,右值表示装入该物品后背包物品的总价值,此处j-weight[i]就证实了这一点,接着我们比较二者,找出最大值
通过这样不断的比较,就可以得到背包容量为某个值时背包物品总价值的最大值,这样不断地用之前的最大值找到当前状态下价值的最大值,就可以找到答案
代码:
#include<iostream>
using namespace std;
int main()
{
int weight[5]={0,2,3,4,5};
int value[5]={0,3,4,5,6};
int N=4,V=8;
int f[5][9]={0};
for(int i=1;i<=N;i++){
for(int j=V;j>=0;j--){
if(weight[i]>j){
f[i][j]=f[i-1][j];
continue;
}
f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
}
}
cout<<f[N][V];
return 0;
}
当然,最后的输出结果为10
算法分析和优化:
该算法的时间复杂度和空间复杂度均为O(N*V),而空间复杂度可以进一步优化:
我们发现,当第i次循环时,我们的f[i][j]为第i件物品(也就是当前物品)装或不装后的最大价值,事实上,如果只保留一个一维数组f[v],在外循环第i次时,同样可以表示第i件物品装或不装后的最大价值
代码如下:
#include<iostream>
using namespace std;
int main()
{
int weight[5]={0,2,3,4,5};
int value[5]={0,3,4,5,6};
int N=4,V=8;
int f[9]={0};
for(int i=1;i<=N;i++){
for(int j=V;j>=0;j--){
if(weight[i]>j){
continue;
}
f[j]=max(f[j],f[j-weight[i]]+value[i]);
}
}
cout<<f[V];
return 0;
}
结果输出10
完全背包问题
问题如下:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是weight[i],价值是 value[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
与01背包的唯一不同就是,完全背包每件物品可以不限次拿
具体思路:
根据以上01背包的思路,这里我们直接给出状态方程再解释
f[i][j]=max(f[i-1][j],f[i-1][j-k*weight[i]]+k*value[i]),(k*weight[i]<=j)
很容易理解,因为每件物品都是无限个,因此对于第i件物品有k+1种方案,即不装和装k件,当然要注意方程后面k的范围
于是我们给出以下暴力代码:
for(int i=1;i<=N;i++){
for(int j=V;j>=0;j--){
for(int k=0;k<=j/weight[i];k++){
f[i][j]=max(f[i-1][j],f[i-1][j-k*weight[i]]+value[i]);
}
}
}
cout<<f[N][V];
再根据01背包中的空间优化,我们可以这样写:
for(int i=1;i<=N;i++){
for(int j=V;j>=0;j--){
for(int k=0;k<=j/weight[i];k++){
f[j]=max(f[j],f[j-k*weight[i]]+k*value[i]);
}
}
}
cout<<f[V];
我们还是这样举例,
编号:1 2 3 4
质量:2 3 4 5
价值:3 4 5 6
背包总容量:8
可以列出如下表格:
根据该表格,我们可以列出以下状态转移方程:
f[i][j]=max(f[i-1][j],f[i][j-weight[i]]+value[i]);
注意,此处max()里的右值中为f[i][j-weight[i]]+value[i],注意和01背包(f[j-weight[i]]+value[i])有所区别!
再进行空间优化:
f[j]=max(f[j],f[j-weight[i]]+value[i]);
(⊙o⊙)?,与01背包惊人的一致!!!
然而,我们可以发现,01背包问题的推导是逆向遍历,也就是说,f[j]用到的是上一条的旧数据(可以理解为利用了j-1时的最大价值分配法),但是完全背包却是顺向遍历,用的是新数据
因此,虽然二者在空间优化后的状态转移方程一样,但是由于顺序和逆序的问题,二者在最后代码上还是有所区别
完全背包代码:
#include<iostream>
using namespace std;
int main()
{
int weight[5]={0,2,3,4,5};
int value[5]={0,3,4,5,6};
int N=4,V=8;
int f[9]={0};
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){ //注意此处顺向遍历,和01背包有所区别!
if(weight[i]>j){
continue;
}
f[j]=max(f[j],f[j-weight[i]]+value[i]);
}
}
cout<<f[V];
return 0;
}
结果输出12
关于文中时间、空间复杂度优化的补充,可以参考这篇文章:动态规划——背包问题(二)
本文完
作者:Sky6634
出处:https://www.cnblogs.com/Sky6634/p/16491598.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix