博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
动态规划-背包问题
阅读量:4047 次
发布时间:2019-05-24

本文共 3062 字,大约阅读时间需要 10 分钟。

背包问题是一种组合优化的NP完全问题。有N个物品和容量为W的背包,每个物品都有自己的体积w和价值v,求拿哪些物品可以使得背包所装下的物品的总价值最大。如果限定每种物品只能选择0个或1个,则问题称为0-1背包问题;如果不限定物品的数量,则问题称为“无界背包或完全背包”问题。

一、0-1背包问题

可以用动态规划来解决背包问题。以0-1背包为例。我们可以定义一个二维数组dp存储最大值,其中dp[i][j]表示前i件物品体积不超过j的情况下能达到的最大价值;如果我们将物品i放入背包,假设第i件物品体积为w,价值为v,那么我么能得到dp[i][j] = dp[i - 1][j - w] + v, 只需要在便利过程中对着两种情况取最大值即可,总时间和空间复杂度都为O(NW)。

int knapsack(vector
weights, vector
values, int N, int W) { vector
> dp(N + 1, vector
(W + 1, 0)); for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = 1; j <= W; j++) { if (j >= w) { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w] + v); } else { dp[i][j] = dp[i - 1][j]; } } } return dp[N][W];}

0-1背包问题-状态转移矩阵样例

 

可以进一步对0-1背包进行空间优化,将复杂度降低为O(W)。如图所示,假设我们目前考虑物品i=2, 且其体积为w=2, 价值为v=3;对于背包容量j,我们可以得到dp[2][j] = max(dp[1][j], dp[1][j - 2] + 3)。 可以发现我们永远只依赖于上一排i=1的信息,之前算过的其他物品都不需要再使用。因此我们可以去掉dp矩阵的第一个维度,在考虑物品i时变成dp[j] = max(dp[j], dp[j-w] + v)。 这里注意需要逆向便利,这样才能够调用上一行物品i-1时 dp[j-w]的值;若按照从左往右的顺序进行正向遍历,则dp[j-w]的值在遍历到j之前就已经背更新成物品i的值了。

int knapsack(vector
weights, vector
values, int N, int W) { vector
dp(W + 1, 0); for (int i = 1; i <= N; ++i) { int w = weights[i - 1], v = values[i - 1]; for (int j = W; j >= w; j--) { dp[j] = max(dp[j], dp[j - w] + v); } } return dp[W];}

 

二、完全背包问题

完全背包问题中,一个物品可以拿多次。如下图所示:

假设我们便利到物品i=2, 且其体积为w=2, 价值为v=3; 对于背包容量j = 5, 组多只能装下2个该物品。那么我们的状态转移方程就变成了dp[2][5] = max(dp[1][5], dp[1][3] + 3, dp[1][1] + 6)。 如果采用这种方法,假设背包容量无穷大而物体的体积无穷小,我们这里的比较次数也会趋近于无穷大,远超O(NW)的时间复杂度。

怎么解决这个问题呢?我们发现在dp[2][3]的似乎和其实已经考虑过了dp[1][3]和dp[2][1]的情况,而在dp[2][1]的时候也已经考虑过dp[1][1]的情况。因此如下图所示:

对于拿多个物品的情况,我们只需要考虑dp[2][3]即可,即dp[2][5] = max(dp[1][5], dp[2][3]+3). 这样就得到了完全背包问题的状态转移方程:

dp[i][j] = max(dp[i - 1][j], dp[i][j-w] +v), 其与0-1背包问题的差别仅仅是把状态转移方程中的第二个i-1变成了i。

int knapsack(vector
weights, vector
values, int N, int W) { vector
> dp(N + 1, vector
(W + 1, 0)); for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = 1; j <= W; j++) { if (j >= w) { dp[i][j] = max(dp[i - 1][j], dp[i][j - w] + v); } else { dp[i][j] = dp[i - 1][j]; } } } return dp[N][W];}

同样的,我们也可以利用空间压缩将时间复杂度降低为O(W)。这里注意哦我们在便利每一行的时候必须正向便利,因为我们要利用当前物品在第j-w列的信息。

int knapsack(vector
weights, vector
values, int N, int W) { vector
dp(W + 1, 0); for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = w; j <= W; j++) { dp[j] = max(dp[j], dp[j - w] + v); } } return dp[W];}

注意:压缩空间到底需要正向还是逆向遍历呢?物品和体积哪个放在外曾,哪个放在内层呢?这取决与状态转移方程的依赖关系。在思考空间压缩之前,不妨将转移矩阵画出来,方便思考如何进行空间压缩。

如果实在不想仔细思考,有个简单的口诀:0-1背包对物品的迭代放外层,里层的体积或值逆向遍历;完全背包对物品的迭代放里层,外层的体积或价值正向遍历。

转载地址:http://vwwci.baihongyu.com/

你可能感兴趣的文章
Mongodb启动命令mongod参数说明
查看>>
理解Node.js中间件以及Connect 模块
查看>>
Nodejs基础中间件Connect
查看>>
Http头介绍:Expires,Cache-Control,Last-Modified,ETag
查看>>
Nginx+Tomcat实现负载均衡、Redis实现Tomcat session会话共享
查看>>
MySQL集群
查看>>
mongodb mongoexprt 导出数据 json csv格式
查看>>
MySQL MERGE存储引擎 简
查看>>
数据库分片(Sharding)与分区(Partition)的区别
查看>>
node.js递归打印文件目录、文件名
查看>>
本地与远程linux上传下载
查看>>
NodeJS的代码调试和性能调优
查看>>
浅谈V8引擎中的垃圾回收机制
查看>>
引擎V8及优化技术
查看>>
Node.js domain异常捕获的一些实践
查看>>
mac下修改环境变量
查看>>
JVM性能调优
查看>>
JVM调优总结
查看>>
Redis与Memcached的区别
查看>>
WebSocket(1)-- WebSocket API简介
查看>>