背包问题

简介

背包问题一个很著名的动态规划问题,也称为0-1背包问题,很多题都是以此为模板进行魔改的。

问题描述

有N个重量为w1,w2,w3,,,wn且价值为v1,v2,v3,,,vn的物品和一个承重量为W的背包,求让背包里装入的物品具有最大的价值总和的物品子集。

题解思路

看到这种最优解求解的题目,很容易想到动态规划思路。为了设计动态规划算法,需要推导出一个递推关系以构造递推函数,用较小子问题的解的形式来表示背包问题的当前问题的解。

首先考虑一个有前iii(1≤i≤N1 \le i \le N1≤i≤N)个物品定义的实例,物品的重量分别为w1,w2,,,wiw_1,w_2,,,w_iw1​,w2​,,,wi​,价值为v1,v2,,,viv_1,v_2,,,v_iv1​,v2​,,,vi​,背包目前的承重量为jjj(1≤j≤W1 \le j \le W1≤j≤W)。

设F(i,j)F(i,j)F(i,j)为组成该实例最优解的物品的总价值,也就是说能够放进承重量为jjj的背包中的前iii个物品中最有价值的子集的总价值。(注意,在这里还没有出现题目想要的解的形式,)

在这里,可以把前iii个物品中能够放进承重量为jjj的背包中的子集分为两种类别:包括第iii个物品的子集和不包括第iii个物品的子集。于是可以得到以下结论:

  • 根据定义,在不包括第iii个物品的子集中,最优子集的价值是F(i−1,j)F(i-1,j)F(i−1,j)。
  • 在包括第i个物品的子集中(j−wi≥0j-w_i \ge 0j−wi​≥0),最优子集为该物品和前i−1i-1i−1个物品中能够放进承重量为j−wij-w_ij−wi​的背包的最优子集组成。这种最优子集的总价值等于vi+F(i−1,j−wi)v_i+F(i-1,j-w_i)vi​+F(i−1,j−wi​)。

因此,在前i个物品中,最优解的总价值等于以上两种情况的最大值,这就是最优子结构了。当然,如果第iii个物品不能放进背包中,那么从前i个物品中选出的最优子集的总价值即等于从前i−1i-1i−1个物品中选出的最优子集的总价值。于是得到下面的递推式,也是状态转移函数
F(i,j)=max{F(i−1,j),vi+F(i−1,j−wi)},j−wi>=0F(i,j)=max\{F(i-1,j),v_i+F(i-1,j-w_i)\},j-w_i>=0F(i,j)=max{F(i−1,j),vi​+F(i−1,j−wi​)},j−wi​>=0
F(i,j)=F(i−1,j),j−wi<0F(i,j)=F(i-1,j),j-w_i<0F(i,j)=F(i−1,j),j−wi​<0

同时可以得到边界
F(0,j)=0,j>=0F(0,j)=0,j>=0 F(0,j)=0,j>=0
F(i,0)=0,i>=0F(i,0)=0,i>=0 F(i,0)=0,i>=0

至此,动态规划的三要素寻找完成,我们的目标不仅仅是求F的值,还有FFF取值时的物品组合。

实例分析

假设物品数量为5,背包承重量为10,各个物品信息如下表。

物品编号 重量w 价值v
1 2 6
2 2 3
3 6 5
4 5 4
5 4 6

动态规划的原理与分治法常常类似,但是分治法将子问题和子子问题反复求解多次,动态规划擅长的则是记录之前问题的解,即填表。

承重量
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 6, 6, 6, 6, 6, 0, 6, 0, 6]
[0, 0, 0, 0, 9, 9, 9, 0, 0, 0, 9]
[0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 14]
[0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 14]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15]

可以看到,当输入为5,10的时候,最优解为15,这是正确的。

回溯求序列

一般,动态规划用来求最优解,利用递归构造可以很方便的找到最后的答案。注意:一旦,动态规划的题目出现输入序列,那么就不是找到答案那么简单,除了正向找到最优解,构造解表,还要反向寻找最优解的构成。 以上面这个例子为例,最后rst[5,10]是15,这是答案,此时定义一个物品长度的序列x并使每个元素为False。

对表格的每一行做遍历,指定w为最高权重10。逆序遍历表格每一行,最后一行根据函数判断rst[m][w](m为遍历下标)是否等于rst[m-1][w]若相等,则代表当前行对应的物品没有选中(原理是依据状态转移函数),否则代表选中,w-=当前行对应物品的权重x[m]置为True,按此顺序,遍历结束。输出为True的对应下标,即为最优解序列。在这个过程中x的取值要么为True要么为False,这就是为什么叫做0-1背包问题。

def bag(i, j, w, v, out):if i == 0 and j >= 0:out[i][j] = 0return 0if i >= 0 and j == 0:out[i][j] = 0return 0if j - w[i-1] >= 0:out[i][j] = max(bag(i-1, j, w, v, out), v[i-1] + bag(i-1, j-w[i-1], w, v, out))return out[i][j]if j - w[i-1] < 0:out[i][j] = bag(i-1, j, w, v, out)return out[i][j]def search(rst, i, j):x = [False for m in range(i+1)]for m in range(i, -1, -1):if rst[m][j] == rst[m-1][j]:# 此时代表没有选中当前passelse:x[m] = Truej -= w[m-1]for i in range(len(x)):if x[i]:print("选择了第{}个物品".format(i))if __name__ == '__main__':# 物品数目i = 5# 背包容量j = 10# 物品信息w = [2, 2, 6, 5, 4]v = [6, 3, 5, 4, 6]# 结果存放表rst = [[0 for m in range(j+1)] for n in range(i+1)]# 计算结果,构造解集合bag(5, 10, w, v, out=rst)# 输出解集合表格for item in rst:print(item)# 回溯查找输入序列search(rst, i, j)

当然本题也可以使用非递归的方式实现,也就是我们更常用的DP Table的形式,代码如下。

def bag(N, W, w, v, dp):for i in range(1, N+1):for j in range(1, W+1):if w[i-1] <= j:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])else:# 第i个物品放不下dp[i][j] = dp[i-1][j]return dpdef search(rst, i, j):x = [False for m in range(i+1)]for m in range(i, 0, -1):if rst[m][j] == rst[m-1][j]:# 此时代表没有选中当前passelse:x[m] = Truej -= w[m-1]for i in range(len(x)):if x[i]:print("选择了第{}个物品".format(i))if __name__ == '__main__':# 物品数目i = 5# 背包容量j = 10# 物品信息w = [2, 2, 6, 5, 4]v = [6, 3, 5, 4, 6]# 结果存放表dp = [[0 for m in range(j+1)] for n in range(i+1)]# 计算结果,构造解集合bag(5, 10, w, v, dp)# 输出解集合表格for item in dp:print(item)# 回溯查找输入序列search(dp, i, j)

优化思路

其实可以从代码和状态转移公式都能看出来当前层的状态只与上一层状态有关(可以参考上一节的DP Table可视化),也就是说,我们其实只需要知道最后一层的情况而不需要存储之前的结果,在dp table中我们最后输出的其实是最右下角的值。这就是01背包的状态压缩。

这个时候,我们可以得到一个新的递推式,这里的f[j−w[i]]+v[i]f[j-w[i]] + v[i]f[j−w[i]]+v[i]代替了原来递推式中的dp[i−1][j−w[i]]+v[i]dp[i-1][j-w[i]]+v[i]dp[i−1][j−w[i]]+v[i]这部分。

f[j]=max{f[j],f[j−w[i]]+v[i]}f[j] = max\{ f[j], f[j-w[i]] + v[i] \} f[j]=max{f[j],f[j−w[i]]+v[i]}

但是注意,我们这里只有一维数组,这就要保证当我想要取得f[j−w[i]]f[j-w[i]]f[j−w[i]]的时候,他没有被更新过,换言之,它必须是上一层的数据,这就要求我们内部的那个循环必须逆序进行。 这就是空间优化版本最难以理解的地方。

具体代码如下,此时就不能采用上面的思路输出选择的物品了。此时空间复杂度由O(NW)O(NW)O(NW)优化为O(W)O(W)O(W)。

def bag(N, W, w, v, dp):for i in range(N):for j in range(W, w[i]-1, -1):dp[j] = max(dp[j], dp[j-w[i]] + v[i])return dpif __name__ == '__main__':# 物品数目i = 5# 背包容量j = 10# 物品信息w = [2, 2, 6, 5, 4]v = [6, 3, 5, 4, 6]# 结果存放表dp = [0 for m in range(j+1)]# 计算结果,构造解集合dp = bag(i, j, w, v, dp)print(dp[-1])

补充说明

具体代码可以查看我的Github,欢迎Star或者Fork。参考书《你也能看得懂的Python算法书》,书中略微有一点不合理之处,做了修改。到这里,其实你已经体会到了动态规划的简约之美。

动态规划算法-03背包问题相关推荐

  1. 动态规划算法解决背包问题

    一.动态规划算法概述 动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从子问题解得到原问题解. 但是经分解得到的子问题往往不是互相独立的.不同子问题的数目常常 ...

  2. 动态规划算法-07背包问题进阶

    简介 我们在本专栏之前的文章介绍了基础的01背包问题及其解题思路,本文我们将讲述其拓展题型,也就是完全背包问题和多重背包问题. 01背包问题 首先,我们先来简单回顾一下经典的01背包问题,关于01背包 ...

  3. 算法导论——动态规划:0-1背包问题(完全解)

    2019独角兽企业重金招聘Python工程师标准>>> package org.loda.dynamic;import org.junit.Test;/*** * @ClassNam ...

  4. 详解:动态规划算法【Java实现】——背包问题

    动态规划 动态规划算法介绍 动态规划算法最佳实践-背包问题 思路分析: 图解分析: ​Java代码实现: 动态规划算法介绍 1)动态规划(Dynamic Programming)算法的核心思想是:将大 ...

  5. 完全背包问题贪心算法c语言,数据结构与算法学习之路:背包问题的贪心算法和动态规划算法...

    一.背包问题描述: 有N种物品和一个重量为M的背包,第i种物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大. 二.解决方法: 1.贪心算 ...

  6. 总结——01背包问题 (动态规划算法)

    0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi . 问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大? 分析一波,面对每个物品,我 ...

  7. 01背包问题-动态规划算法(最简洁)

    动态规划算法: 思路:从第一个物品开始填表(m[i][j]),从左到右,从上到下 如果物品比背包容量大,即放不下,则m[i][j]=m[i-1][j] 如果物品比背包容量下,即放得下,则比较 放与不放 ...

  8. 探讨与研究——动态规划算法、回溯法、分支限界法解0-1背包问题

    一个人终归是要成长的,是要不断历练的,没有人可以安安稳稳一辈子.就算是最有地位最有钱的人也要不断追求.不断历练.不断提升自己. 人的学问少时在不断学习,青年时期不断实践.随着时间推移,到了老年终有所成 ...

  9. Java使用动态规划算法思想解决01背包问题

    Java使用动态规划算法思想解决背包问题 背包问题是一种组合优化的NP完全问题.问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高 动 ...

最新文章

  1. 对象④for in 循环
  2. nyoj203(迪杰斯特拉+01背包)
  3. mysql 连接数的最大数
  4. 他受他爸影响,他爸受数学家影响,最终造出了自动旋转的房子!
  5. mysql增量备份实例_MySQL增量备份与恢复实例
  6. WinAPI: 钩子回调函数之 CallWndProcRetProc
  7. python做一个本地搜索工具_用Python打造一款文件搜索工具,所有功能自己定义!...
  8. Unity3D面试题汇总
  9. MJKDZ PS2手柄(BK2461):驱动安装问题及解决
  10. 研究:信仰能帮助人坦然面对挫折
  11. qiankun加载react子应用报错[import-html-entry]: error occurs while executing normal script
  12. 无他 唯手熟尔 this指针
  13. Fedora 12下使用HP USB打印机(HP laserjet P1007)
  14. pb开发的程序win10 报 sql server request resulted in a bad return code or status 的解决办法
  15. LPC1768 UART超时中断的使用
  16. ajax与spry的关系,10.4 Spry Ajax Framework初探
  17. python绘制语谱图(详细注释)
  18. GCC背后的故事OpenCV相逢何必曾相识
  19. 使用AFNetworking
  20. 网络综合布线线缆如何敷设布放_网络综合布线中穿线工作技术要求

热门文章

  1. Buffer的基本的原理
  2. MyBatis 哪些地方用到了代理模式?
  3. 我如何看Spring5?
  4. Redis基本数据类型
  5. Btree索引和Hash索引
  6. Oracle数据库管理
  7. 模板方法模式coding
  8. 内存溢出与内存泄漏区别
  9. URL编码 - Java加密与安全
  10. 数据拆分缺点和解决方案