动态规划之0,1背包问题
我们在上一篇文章初识动态规划已经对动态规划的算法思想有了一定的了解,今天我们再来通过一个经典问题:0,1背包问题,从更深层次的角度来认识一下动态规划算法。建议先看上一篇文章,再来看这篇。
首先,我们来看一下什么是0,1背包问题。
问题描述:
给定 n 件物品,物品的重量分别为w1、w2、w3....,现需要挑选物品放入背包中,
假定背包能承受的最大重量为V,问应该如何选择装入背包中的物品,使得装入背包
中物品的的重量最大?
首先,我们最直观的想法就是,穷举所有可能的装法,然后从中选出满足条件的最大值。我们可以使用回溯算法来实现。如下所示:
class BagQ: #假设物品的重量都大于0 #背包的最大承重也大于0 maxW=0 weight=[2,2,8,3,5,3] #物品重量 n=6 #物品个数 w=10 #背包的最大承重 def getMax(self,i,cw): if cw==self.w or i==self.n: #背包装满或者物品被考察完了 if cw>self.maxW: self.maxW=cw return self.getMax(i+1,cw) #第i个物品不放入背包 #考察放入第i个物品后,会不会超过背包的容量 if cw+self.weight[i]<=self.w: self.getMax(i+1,cw+self.weight[i]) #选择装第i个物品 bag=BagQ() bag.getMax(0,0) print(bag.maxW)
我们通过代码可以看到回溯算法的时间复杂度较高,是指数级别的。那有什么方法可以降低时间复杂度吗?我们最好的方式就是把递归调用树画出来,来找找规律。递归调用树如下所示:
递归树的每个节点表示一种状态,用(i,w)来表示。比如f(1,2)表示第一个物品放入背包,此时背包的重量为2,下一步f(2,2)表示第二个物品不放入背包,此时背包的重量不变。而f(2,4)表示第二个物品放入背包,此时背包的重量为4。
从上图我们可以发现,会有重复的子问题出现,比如f(2,2)被计算了两次,那我们该如何避免重复计算呢?
我们可以这么来看,我们把整个求解阶段分为n个阶段,每个阶段去决策一个物品是否放入背包。每个物品决策完之后,对应的背包中物品的重量会有多种可能,也就是多种状态。
我们来一步一步分析。
-
第一个物品的重量为2,我们先来决策第一个物品是否放入背包,它有两种可能,要么放入,要么不放,与之相对应的背包的重量也有两种可能,要么是0,要么是2。
-
第二个物品的重量为2,我们再来决策第二个物品是否放入背包,它也有两种可能,要么放入,要么不放,与之相对应的背包的重量就不是两种可能了,它有4种可能(我们的排列组合知识可以派上用场了)。它需要依赖于上个物品是否放入背包,所以它是需要依赖于上一个状态的。
.....
从上面的分析来看,第n个阶段背包的状态是需要依赖于第n-1个阶段的,所以我们需要把上一个阶段的状态保存下来,才能快速的求出这个阶段的状态,因此状态转移矩阵就出来了。我们这里需要定义一个二维的数组,来记录不同阶段的状态。如下图所示:
下面我们来看代码是如何实现的:
def bag(weight,n,w): status=[[0 for _ in range(w+1)] for _ in range(n)] status[0][0]=1 if(weight[0]<=w): status[0][weight[0]]=1 for i in range(n): #动态规划状态转移 #不把第i个物品放入背包 for j in range(w+1): if status[i-1][j] == 1: status[i][j] = 1 #把第i个物品放入背包 for j in range(w+1-weight[i]): if status[i-1][j] == 1: status[i][j+weight[i]] = 1 #输出结果 print(status) for i in range(w,-1,-1): if status[n-1][i]==1: return i return 0 weight=[2,2,8,3,5,3] n=6 w=10 print(bag(weight,n,w))
我们通过把问题分解为多个阶段,每个阶段对应一个决策。然后记录下每一个阶段可达的状态集合(去掉重复的),然后通过当前阶段的状态集合,来推导下一个阶段的状态集合,依次前进,从而把问题解决。
接下来,我们再来把0,1背包问题升级一下,引入物品价值这一说。也就是针对一组不同价值、不同重量的物品,我们将物品放入背包中,在满足背包最大重量的限制条件下,背包中可装入物品的总价值最大是多少呢?这个思路和上一个思路类似,我这里就不在赘述。建议大家先用回溯算法实现,然后画出递归树,最后写出状态转移矩阵,再实现代码。我这里直接给出代码。如果有问题,欢迎大家留言。
def bag(weight,value,n,w): status=[[-1 for _ in range(w+1)] for _ in range(n)] status[0][0]=0 if(weight[0]<=w): status[0][weight[0]]=value[0] for i in range(n): #动态规划状态转移 #不把第i个物品放入背包 for j in range(w+1): if status[i-1][j] >= 0: status[i][j] = status[i-1][j] #把第i个物品放入背包 for j in range(w+1-weight[i]): if status[i-1][j] >= 0: v=status[i-1][j]+value[i] if(v>status[i][j+weight[i]]): status[i][j+weight[i]]=v #输出结果 print(status) maxV=0 for i in range(w+1): if status[n-1][w]>maxV: maxV=status[n-1][w] return maxV weight=[2,2,8,3,5,3] value=[3,4,12,6,3,2] n=6 w=10 print(bag(weight,value,n,w))
经过这篇文章和上一篇文章,我们应该对动态规划有了一个清晰的认识,我会在下一篇把问题抽象一下,看哪类问题适合动态规划来解决,以及解决动态规划问题的思考过程是怎么样的?为了不错过,请关注公众号。
原文地址:https://www.cnblogs.com/laohanshuibi/p/15138882.html
- 区块链与数字货币是什么关系呢?
- 保存数据到MySql数据库——我用scrapy写爬虫(二)
- 人工智能将取代人类?危机亦或是新的机遇
- 大数据驱动的未来网络:体系架构与应用场景(下)网络架构与场景详解
- 冷静点,NVIDIA 禁止 Geforce 进数据中心想限制的并不是深度学习
- 智能机器人崛起背后的中国力量
- 企业微服务架构转型-实施步骤
- Andrew Ng机器学习课程笔记--week2(多元线性回归&正规公式)
- 科技第六感:黑客控制你的车!不信?其实很简单
- python多版本的pip共存问题解决办法
- C++ 对vector进行排序
- 小程序优化36计
- 神经网络权重初始化问题
- Andrew Ng机器学习课程笔记--week11(图像识别&总结划重点)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python 句法错误:"SyntaxError: invalid character in identifier",原因及解决方法
- Python3 多线程问题:ModuleNotFoundError: No module named 'thread',原因及解决办法。
- 文件传输和秒传
- 关于数据库的各种备份与还原姿势详解
- Python 技术篇-多线程的2种创建方法,多线程的简单用法,快速上手。
- Python 技术篇-调用浏览器访问指定网页,一行代码实现。非Selenium。
- 数据库热备份神器 - XtraBackup
- Python 技术篇-读取文件,将内容保存dict字典中。去掉字符串中的指定字符方法。dict字典的遍历。
- PyQt5 技术篇-plainTextEdit控件获得文本内容方法、设置文本内容方法。
- PyQt5 技术篇-鼠标移动控件显示提示,Qt Designer控件提示设置方法。
- PyQt5 技术篇-窗口名、窗口图标的设置方法。
- 101个shell脚本
- PyQt5 图片兼容性问题:"libpng warning: bKGD: invalid.",原因及解决办法。
- 编程语言经典小例题—Python版【持续更新】
- Python+selenium 自动化-切换窗口页签、切换iframe框架。确定页面是否包含iframe方法。