01背包问题详解(浅显易懂)
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背包问题详解(浅显易懂)相关推荐
- 九种 0-1 背包问题详解
目录 动态规划概念 问题1:0-1背包问题 问题2:完全背包问题 问题3:多重背包问题 问题4:混合背包问题 问题5:二维背包问题 问题6:分组背包问题 问题7:有依赖的背包问题 (困难) 问题8:背 ...
- 0-1背包问题详解(一步一步超详细)
1.什么叫01背包问题? 背包问题通俗的说,就是假如你面前有5块宝石分别为a, b, c, d, e,每块宝石的重量不同,并且每块宝石所带来的价值也不同(注意:这里宝石的重量的价值没有特定关系),目前 ...
- 0-1背包问题详解(DP分支限界回溯三种方法)
0-1背包 将n个项目的权重和值,放入一个容量为W的背包中,得到背包中最大的总价值.换句话说,给定两个整数数组val[0..n - 1]和wt [0 . .n-1],分别表示与n个项目相关的值和权重. ...
- 动态规划解决0-1背包问题详解(图文并茂)
动态规划解决0-1背包问题 这个是网上比较好的案例,因为原文有些地方晦涩难懂,对于刚接触动态规划问题的朋友来说很不友好,所以很对地方加入了我自己的见解,也是作为我的一次学习历程. 一.问题描述: 有n ...
- 0-1背包问题详解-动态规划-两种方法
问题描述: 给定n种物品和一背包.物品i的重量为wi,其价值为vi, 背包容量为c.问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大? 解析: 此问题形式化的描述是,给定c > 0, ...
- 动态规划专题 01背包问题详解【转】
对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本 ...
- 动态规划--01背包问题详解
代码随想录day42和day43 动态规划 模块01背包问题 "即使到不了远方,心中也要有远方的模样." 文章目录 1. 01背包理论基础 1.1什么是背包问题 1.2二维dp数组 ...
- 动态规划之01背包问题详解
文章目录 01背包的概念 母题(01背包模板题) 例题一(装箱问题) 例题二(数字组合) 例题三(背包问题求方案数) 例题四(背包问题求具体方案数) 01背包的概念 首先我们要明确的是01背包的概念 ...
- 动态规划---01背包问题详解
与利润有关的背包问题. 问题描述: 一个商人带着一个能装m千克的背包去乡下收购货物,准备将这些货物卖到城里获利. 现有n种货源,且知第i种货物有wi千克,可获利pi元.请编写算法帮助商人收购货物,以获 ...
最新文章
- react非常适合入门者学习使用的后台管理框架
- boost::hana::if_用法的测试程序
- src-d/gogit 使用
- spring学习(33):id和name
- python动态映射_sqlalchemy动态映射
- PHP单选框实现的方法,jQuery简单实现遍历单选框的方法
- 软件开发需要学好数学吗?
- vue.js环境部署
- 【转】mybatis在xml文件中处理大于号小于号的方法
- 反作弊基本概念与机器学习的应用(1)
- java语言的数组描述_下列关于java语言的数组描述中,错误的是( )
- 用Ai制作立体logo
- Hive分组统计前top N条记录
- 【TLD】改进后的TLD视频目标跟踪方法的MATLAB仿真
- 集成学习(Bagging和Boosting)
- 基因数据分析主流软件与基因预测方法步骤-搬运工
- <textarea></textarea> placeholder属性不显示
- 2022安全员-C证考试题模拟考试题库及模拟考试
- 系统架构设计 2.1 管道-过滤器风格
- 英语 作文模板二 图表作文