01背包问题详解

01背包是一种动态规划问题。动态规划的核心就是状态转移方程,本文主要解释01背包状态转移方程的原理。

问题描述

01背包问题可描述为如下问题:
有一个容量为V的背包,还有n个物体。现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里。每个物体都有两个属性,即体积w和价值v。
问:如何向背包装物体才能使背包中物体的总价值最大?

为什么不用贪心?

我在第一次做这个题目时考虑的是贪心算法。所谓贪心问题,就是每一步决策都采取最优解,按照此方案最后结果也是最优解。
为什么这个问题不能用贪心呢?
举个例子
我的背包容量为10,而且有4个物体,它们的体积和价值分别为
w1 = 8, v1 = 9
w2 = 3, v2 = 3
w3 = 4, v3 = 4
w4 = 3, v4 = 3
贪心是每一步采取最优拿法,即每一次都优先拿价值与体积比值最大的物体
c1 = v1/w1 = 1.125(最大)
c2 = v2/w2 = 1
c3 = v3/w3 = 1
c4 = v4/w4 = 1
所以优先拿第一个物体,随后背包再也装不下其他物体了,则最大价值为9。
但是这个问题的最优解是取物体2,3,4装进背包,最大价值为3+4+3=10!!!
所以这个问题不可以用贪心法来处理。

原始的 01背包

01背包的状态转移方程为
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[j])

i代表对i件物体做决策,有两种方式—放入背包和不放入背包。
j表示当前背包剩余的容量。

转移方程的解释:
创建一个状态矩阵f,横坐标 i 是物体编号,纵坐标 j 为背包容量。
首先将 f 第0行和第0列初始化为0 (代码里面将整个f初始化为0了,其实只初始化第0行和第0列就够了)。这个表示不放物体时最大价值为0 。(物体编号从1开始)
接下来依次遍历f的每一行。如下所示。

for (int i = 1; i <= n; i++)
{for (int j = V; j >= 0; j--){if (j >= w[i])//如果背包装得下当前的物体{f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);}else//如果背包装不下当前物体{f[i][j] = f[i - 1][j];}}
}

如果背包装得下当前的物体,在遍历过程中分别计算第i件物体放入不放入背包的价值,取其中大的做为当前的最大价值。
如果背包装不下当前物体那么第i个物体只有不放入背包一种选择。

不放入背包时:第i次决策后的最大价值和第i-1次决策时候的价值是一样的(还是原来的那些物体,没多没少)。
放入背包时:第i次决策后的价值为 第i-1次决策时候的价值 加上 当前物体的价值v[j]物体放入背包后会使背包容量变为 j即没放物体之前背包的容量为j - w[i]


#include <iostream>
#include <vector>
using namespace std;
#define max(N1,N2) N1>N2?N1:N2
int main()
{/*第一行输入背包容量V和物体的个数n接下来有n行,每行包含两个数字,分别为该物体的花费和价值*/vector<int> w, v;//w为花费,v为价值vector<vector<int>> f;//f状态矩阵int V, n;//V背包容量,n物体数while (cin >> V >> n){w.clear();v.clear();f.clear();w.push_back(0);v.push_back(0);//输入原始数据for (int i = 1; i <= n; i++){int cur_w, cur_v;cin >> cur_w >> cur_v;w.push_back(cur_w);v.push_back(cur_v);}//初始化状态矩阵for (int i = 0; i <= n; i++){vector<int> buff(V + 1, 0);f.push_back(buff);}//动态规划过程for (int i = 1; i <= n; i++){for (int j = V; j >= 0; j--){if (j >= w[i]){f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);}else{f[i][j] = f[i - 1][j];}}}//输出答案int ans = f[n][V];cout << ans << endl;}return 0;
}

优化空间复杂度的 01背包

未优化时候状态转移方程为
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[j])
遍历过程为

for (int i = 1; i <= n; i++)
{for (int j = V; j >= 0; j--){if (j >= w[i]){f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);}else{f[i][j] = f[i - 1][j];}}
}

可以发现如下问题:
(1)状态表f的遍历顺序为从第1行开始一行一行遍历,且在遍历第i行时候不会用到第i-2行数据,也就是i-2行及以前的数据没有用了,可以清除。同时,第i-1行的数据每个只会用到一次。
(2)遍历每一行时候只用到当前容量j和j-w[i]的数据,也就是第 i 次遍历只需要 第 i-1 次遍历中容量小于等于 j 的数据 。

所以我们可以按照如下方法优化f的空间复杂度:

f[j] = max(f[j], f[j - w[i]] + v[j])
for (int i = 1; i <= n; i++)
{for (int j = V; j >= 0; j--){if (j >= w[i]){f[j] = max(f[j], f[j - w[i]] + v[i]);}else{f[j] = f[j];}}
}

从本质上说,这种优化方法针对了上述的两个问题:
(1)把遍历第i个物体和遍历第i-1个物体时的最大价值存在一个单元里。更新前f[j]存i-1的价值,更新后f[j]存i的价值。因为用不到i-2及以前的数据所以不需要存。因为以后不会再用到i-1的价值所以被覆盖了没问题
(2)j从背包容量V开始遍历,即从大到小遍历,保证了当前f[j]和f[j - w[i]]里面存的是i-1的数据,即等价于f([i])[j] = max(f([i - 1])[j], f([i - 1])[j - w[i]] + v[i]),从而和优化空间复杂度前状态转移方程的原理一致。
但仍存在一些问题,比如

else
{f[j] = f[j];
}

自己给自己赋值,是无用操作,所以j < w[i]时候什么都不做即可。换句话说,只需要遍历到j >= w[i],从而得到

for (int i = 1; i <= n; i++)
{for (int j = V; j >= w[i]; j--){f[j] = max(f[j], f[j - w[i]] + v[i]);}
}

最终代码如下

/*#include <iostream>
#include <vector>
using namespace std;
#define max(N1,N2) N1>N2?N1:N2
int main()
{/*第一行输入背包容量V和物体的个数n接下来有n行,每行包含两个数字,分别为该物体的花费和价值*/vector<int> w, v;//w为花费,v为价值vector<int> f;//f状态矩阵int V, n;//V背包容量,n物体数while (cin >> V >> n){w.clear();v.clear();f.clear();w.push_back(0);v.push_back(0);//输入原始数据for (int i = 1; i <= n; i++){int cur_w, cur_v;cin >> cur_w >> cur_v;w.push_back(cur_w);v.push_back(cur_v);}//初始化状态矩阵f = vector<int>(V + 1, 0);//动态规划过程for (int i = 1; i <= n; i++){for (int j = V; j >= w[i]; j--){f[j] = max(f[j], f[j - w[i]] + v[i]);}}//输出答案int ans = f[V];cout << ans << endl;}return 0;
}

01背包问题详解(浅显易懂)相关推荐

  1. 九种 0-1 背包问题详解

    目录 动态规划概念 问题1:0-1背包问题 问题2:完全背包问题 问题3:多重背包问题 问题4:混合背包问题 问题5:二维背包问题 问题6:分组背包问题 问题7:有依赖的背包问题 (困难) 问题8:背 ...

  2. 0-1背包问题详解(一步一步超详细)

    1.什么叫01背包问题? 背包问题通俗的说,就是假如你面前有5块宝石分别为a, b, c, d, e,每块宝石的重量不同,并且每块宝石所带来的价值也不同(注意:这里宝石的重量的价值没有特定关系),目前 ...

  3. 0-1背包问题详解(DP分支限界回溯三种方法)

    0-1背包 将n个项目的权重和值,放入一个容量为W的背包中,得到背包中最大的总价值.换句话说,给定两个整数数组val[0..n - 1]和wt [0 . .n-1],分别表示与n个项目相关的值和权重. ...

  4. 动态规划解决0-1背包问题详解(图文并茂)

    动态规划解决0-1背包问题 这个是网上比较好的案例,因为原文有些地方晦涩难懂,对于刚接触动态规划问题的朋友来说很不友好,所以很对地方加入了我自己的见解,也是作为我的一次学习历程. 一.问题描述: 有n ...

  5. 0-1背包问题详解-动态规划-两种方法

    问题描述: 给定n种物品和一背包.物品i的重量为wi,其价值为vi, 背包容量为c.问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大? 解析: 此问题形式化的描述是,给定c > 0, ...

  6. 动态规划专题 01背包问题详解【转】

    对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本 ...

  7. 动态规划--01背包问题详解

    代码随想录day42和day43 动态规划 模块01背包问题 "即使到不了远方,心中也要有远方的模样." 文章目录 1. 01背包理论基础 1.1什么是背包问题 1.2二维dp数组 ...

  8. 动态规划之01背包问题详解

    文章目录 01背包的概念 母题(01背包模板题) 例题一(装箱问题) 例题二(数字组合) 例题三(背包问题求方案数) 例题四(背包问题求具体方案数) 01背包的概念  首先我们要明确的是01背包的概念 ...

  9. 动态规划---01背包问题详解

    与利润有关的背包问题. 问题描述: 一个商人带着一个能装m千克的背包去乡下收购货物,准备将这些货物卖到城里获利. 现有n种货源,且知第i种货物有wi千克,可获利pi元.请编写算法帮助商人收购货物,以获 ...

最新文章

  1. react非常适合入门者学习使用的后台管理框架
  2. boost::hana::if_用法的测试程序
  3. src-d/gogit 使用
  4. spring学习(33):id和name
  5. python动态映射_sqlalchemy动态映射
  6. PHP单选框实现的方法,jQuery简单实现遍历单选框的方法
  7. 软件开发需要学好数学吗?
  8. vue.js环境部署
  9. 【转】mybatis在xml文件中处理大于号小于号的方法
  10. 反作弊基本概念与机器学习的应用(1)
  11. java语言的数组描述_下列关于java语言的数组描述中,错误的是( )
  12. 用Ai制作立体logo
  13. Hive分组统计前top N条记录
  14. 【TLD】改进后的TLD视频目标跟踪方法的MATLAB仿真
  15. 集成学习(Bagging和Boosting)
  16. 基因数据分析主流软件与基因预测方法步骤-搬运工
  17. <textarea></textarea> placeholder属性不显示
  18. 2022安全员-C证考试题模拟考试题库及模拟考试
  19. 系统架构设计 2.1 管道-过滤器风格
  20. 英语 作文模板二 图表作文

热门文章

  1. java 获得header,Java获取Http响应Header信息
  2. 使用MQTT实现简单命令控制
  3. Pgsql实现SQLServer的datediff函数
  4. python特殊方法和特殊属性
  5. C语言实现坑爹大冒险游戏(附完整源码)
  6. css实现3D旋转效果
  7. Command Msiexec Install
  8. 碰到小乞丐你怎么办???
  9. 机器学习——正则化理论(Regularization Theory)
  10. SAP ABAP me21n 采购订单抬头字段增强 MM06E005