DP动态规划之01背包问题
问题描述
有n个物品,第i种物品的价值为\(p_i\)重量为\(W_i\),选一些物品到一个容量为C的背包里,使得背包内物品在总重量不超过C的前提下,价值尽量大。
问题分析
在之前我们了解贪心思想的时候曾经有过类似的题目那时候物品是可拆分的我们只需要选择单位重量最大的物品即可。但是在这里,每一个物品都是完整的,只能是装或者不装。我们分析,有n个物品,为\(a_1\)...\(a_n\)当对\(a_1\)做决策之后,\(a_2\)...\(a_n\)是类似的问题。发现它可以用递归去解,那样的话,可以用DP去求解吗。如果可以的话我们就需要找到他的决策或者它的子问题。
针对\(a_1\),我们发现当\(W_1\)>C时,说明\(a_1\)无法装进背包,问题变为求\(a_2\)...\(a_n\)装进容量为C的背包求最大价值。当\(W_1\)<C,可以选择装入背包,则问题变为求\(a_2\)...\(a_n\)装进容量为C-\(W_1\)的背包,求最大价值这时候价值变为\(P_1\)+所求。另一种选择是不装入背包这时候问题变为求\(a_2\)...\(a_n\)装进容量为C的背包求最大价值。对于父问题(也就是这个问题)来说,最优值在这两者之间取较大值。然后把这个最优值记录下来。接下来的每一个都是这样的操作(子问题)。最终递归出口就是只剩最后一个,an...an,当\(W_n\)>C时不能装最大的价值就是0,\(W_n\)<=C时,最大价值就是\(P_n\)
问题求解
设dp[i][j]表示从\(a_i\)到\(a_n\)的最大价值。j为背包的剩余容量。所以j这里我们默认为了整数,潜在的,\(W_i\)应该也是整数。(思考:当这里不是整数的话应该怎么办?)
\(dp[i][j]=\left\{
\begin{aligned}
dp[i+1][j],{W_i>C}\\
max{\left\{
\begin{aligned}dp[i+1][j],不装,{W_i<=j}\\
dp[i+1][j-{W_i}]+{P_i},装,{W_i<=j}\end{aligned}\right.}
\end{aligned}
\right.
\)
递归出口(函数初值):
\(dp[n][j]=\left\{
\begin{aligned}
0,{W_n>j}\\
{P_n},{W_n<j}
\end{aligned}
\right.
\)
Java代码实现
/**
* DP问题处理01背包问题
* @param w 物品重量
* @param p 物品价值
* @param C 背包容量
* @param n 物品个数
*/
public static void test(int[] w,int[] p,int C,int n) {
int[][]dp=new int[n+1][C+1];
//初值
for(int i=0;i<=C;i++)
if(i>=w[n])
dp[n][i]=p[n];
//2~n-1
for(int i=n-1;i>=2;i--) {
for(int j=0;j<=C;j++) {
if(w[i]>j)
dp[i][j]=dp[i+1][j];
else {
int temp=dp[i+1][j-w[i]]+p[i];
if(temp>dp[i+1][j])
dp[i][j]=temp;
else
dp[i][j]=dp[i+1][j];
}
}
}
//求1,C
if(w[1]>C)
dp[1][C]=dp[2][C];
else {
int temp=dp[2][C-w[1]]+p[1];
if(temp>dp[2][C])
dp[1][C]=temp;
else
dp[1][C]=dp[2][C];
}
System.out.println("最优值为"+dp[1][C]);
//最优解
int j=C;
for(int i=1;i<n;i++) {
if(dp[i][j]==dp[i+1][j])
{
System.out.println(i+"不放");
}
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(dp[n][j]==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}
优化方向一:时间方面:因为是j是整数是跳跃式的,可以选择性的填表。
(DP它避免了很多重复计算,但有时候会计算无用的子问题就是做了许多无用计算。可以以这种思想进行优化。)
主要想法就是记录计算的路径。因为对于需要计算的每一个(i,j)如果&W_i&<j那么就需要计算(i+1,j)和(i+1,j-&W_i&)。从(1,n)开始推,因为不知道数组的长度选取了ArrayList数据结构,但是不能用其中的迭代器,因为迭代器不能改变在数组变化的过程中。话不多说,代码如下。
public static void test(int[] w,int[] p,int C,int n) {
class Point{
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
//计算所用路径
int[][]flag=new int[n+1][C+1];
ArrayList<Point> list=new ArrayList<Point>();
list.add(new Point(1,C));
int iter=0;
while(iter!=list.size()) {
Point temp = list.get(iter++);
if(temp.x>=n)
break;
if(flag[temp.x+1][temp.y]==0)//没有添加过
{
list.add(new Point(temp.x+1,temp.y));
flag[temp.x+1][temp.y]=1;
}
if(temp.y-w[temp.x]>0&&flag[temp.x+1][temp.y-w[temp.x]]==0)
{
list.add(new Point(temp.x+1,temp.y-w[temp.x]));
flag[temp.x+1][temp.y-w[temp.x]]=1;
}
}
int[][]dp=new int[n+1][C+1];
for(int i=list.size()-1;i>=0;i--) {
Point temp=list.get(i);
if(temp.x==n)
dp[temp.x][temp.y]=w[temp.x]>temp.y?0:p[temp.x];
else {
if(w[temp.x]>temp.y)//装不下
dp[temp.x][temp.y]=dp[temp.x+1][temp.y];
else {
int t=dp[temp.x+1][temp.y-w[temp.x]]+p[temp.x];
dp[temp.x][temp.y]=t>dp[temp.x+1][temp.y]?t:dp[temp.x+1][temp.y];
}
}
}
for(int i=1;i<dp.length;i++) {
for(int j=1;j<dp[0].length;j++) {
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
System.out.println("最优值为"+dp[1][C]);
//最优解
int j=C;
for(int i=1;i<n;i++) {
if(dp[i][j]==dp[i+1][j])
{
System.out.println(i+"不放");
}
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(dp[n][j]==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}
输出了不同的dp数组,结果对比如下:
public static void main(String[] args) {
int[] w= {0,2,1,2,3,3};
int[] p= {0,10,8,9,12,4};
test(w,p,7,5);
}
优化前:
优化后:
思考二:处理j(背包容量),w(重量)不为整数的时候,因为j不为整数了,它就没办法作为数组下标使用。
主要思想:这个想法建立在选择性填表的优化之上,在做选择性填表这个优化的时候,我们将计算路径记录了下来,现在我们也一样先记录计算路径,只不过Point(内部类)中的y值用float表示。然后将dp这个表变为map的数据结构。使用map保存是最重要的就是键的确定,这个键需要保存对应到dp中(i,j)这两个信息。key=i*max+j,当max=max(max(w[i]),C)+1时,针对每一个(i,j)都有确定的唯一key。因为\(i_1*max+j_1=i_2*max+j_2\),(\((i_1-i_2)\)=\(\frac{(j_2-j_1)}{max}\)中右侧<1,左侧在[0,n-1]中取整数,只有(i,j)完全相同时取0)。这个相当于hashmap中的hash函数来映射一样。然后其他的步骤与选择性填表的填表过程类似。代码如下:
public static void test(float[] w,float[] p,float C,int n) {
class Point{
int x;
float y;
public Point(int x, float y) {
this.x = x;
this.y = y;
}
}
//计算函数表达式系数
float max=w[0];
for(int i=1;i<w.length;i++) {
if(w[i]>max)
max=w[i];
}
max=Math.max(C, max)+1;
//计算所用路径
ArrayList<Point> list=new ArrayList<Point>();
Map<Float,Float> map=new HashMap<Float,Float>();
list.add(new Point(1,C));
int iter=0;
while(iter!=list.size()) {
Point temp = list.get(iter++);
if(temp.x>=n)
break;
if(!map.containsKey((temp.x+1)*max+temp.y))
{
list.add(new Point(temp.x+1,temp.y));
map.put((temp.x+1)*max+temp.y, (float) -1.0);
}
if(temp.y-w[temp.x]>=0&&!map.containsKey((temp.x+1)*max+(temp.y-w[temp.x])))
{
list.add(new Point(temp.x+1,temp.y-w[temp.x]));
map.put((temp.x+1)*max+(temp.y-w[temp.x]), (float) -1.0);
}
}
//填表
for(int i=list.size()-1;i>=0;i--) {
Point t=list.get(i);
if(t.x==n)
{
if(w[t.x]>t.y)map.put(t.x*max+t.y, (float) 0);
else map.put(t.x*max+t.y, p[t.x]);
}
else {
if(w[t.x]<=t.y)
{
float ft=p[t.x]+map.get((t.x+1)*max+(t.y-w[t.x]));
if(ft>map.get((t.x+1)*max+t.y))
map.put(t.x*max+t.y, ft);
else
map.put(t.x*max+t.y, map.get((t.x+1)*max+t.y));
}
else
map.put(t.x*max+t.y, map.get((t.x+1)*max+t.y));
}
}
System.out.println("最优值:"+map.get(max+C));
//最优解
float j=C;
for(int i=1;i<n;i++) {
if(map.get(i*max+j)==map.get((i+1)*max+j))//说明没装
System.out.println(i+"不放");
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(map.get(n*max+j)==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}
总结
首先,这个问题的解决从递归到递推,因为i(开始的物品)和j(背包)的存在选用二维数组(i..n,n是一定的,所以只有两个参数)。
其次,我一开始想的是倒着推,从只有一个物品到n个这也是DP问题常用的想法,就是从小问题到大问题。
最后,DP问题的下手点可以先分析出它的子问题,但是这个子问题是来源与决策的。所以,决策也很重要。
DP动态规划之01背包问题的更多相关文章
- 动态规划入门-01背包问题 - poj3624
2017-08-12 18:50:13 writer:pprp 对于最基础的动态规划01背包问题,都花了我好长时间去理解: poj3624是一个最基本的01背包问题: 题意:给你N个物品,给你一个容量 ...
- C++动态规划求解0-1背包问题
问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应该如何选择装入背包的物品,是的装入背包中物品的总价值最大? 细节须知: 暂无. 算法原理: a.最优子结构性质 ...
- 动态规划专题 01背包问题详解 HDU 2546 饭卡
我以此题为例,详细分析01背包问题,希望该题能够为大家对01背包问题的理解有所帮助,对这篇博文有什么问题可以向我提问,一同进步^_^ 饭卡 Time Limit: 5000/1000 MS (Java ...
- nyoj 49-开心的小明(动态规划, 0-1背包问题)
49-开心的小明 内存限制:64MB 时间限制:1000ms Special Judge: No accepted:7 submit:11 题目描述: 小明今天很开心,家里购置的新房就要领钥匙了,新房 ...
- dp入门之01背包问题
...通过暴力手推得到的一点点感觉 动态规划是相对于贪心算法的一种取得最优解的算法,通过对每一步的取舍判断从 0 推到所拥有的第 n 件物品,每次判断可以列写出状态转移方程,通过记忆化相对暴力地取得最 ...
- dp或dfs(01背包问题)
链接:https://ac.nowcoder.com/acm/contest/993/C来源:牛客网题意:n头牛,给出它们的H高度,问这些牛的高度叠加起来大于等于书架高度,问叠加后的高度与书架的差值最 ...
- ACM:动态规划,01背包问题
题目: 有n件物品和一个容量为C的背包.(每种物品均仅仅有一件)第i件物品的体积是v[i],重量是w[i].选一些物品装到这个背包中,使得背包内物品在整体积不超过C的前提下重量尽量大. 解法:两种思路 ...
- 经典DP动规 0-1背包问题 二维与一维
先上代码 b站讲解视频 灯神讲背包 #include <iostream> #include <cstring> #include <algorithm> usin ...
- 0-1背包问题——动态规划求解【Python】
动态规划求解0-1背包问题: 问题:背包大小 w,物品个数 n,每个物品的重量与价值分别对应 w[i] 与 v[i],求放入背包中物品的总价值最大. 动态规划核心:计算并存储小问题的最优解,并将这些最 ...
随机推荐
- D - Romantic
The Sky is Sprite. The Birds is Fly in the Sky. The Wind is Wonderful. Blew Throw the Trees Trees ar ...
- Joomla 3.4.6 Remote Code Execution漏洞复现
0x00:简介 Joomla是一套全球有名的CMS系统. Joomla基于PHP语言加上MySQL数据库所开发出来的WEB软件系统,目前最新版本是3.9.12. Joomla可以在多种不同的平台上部署 ...
- selenium 获取页面<input>标签的个数和各个属性的值
获取页面某个标签的数量.id.name.class的值,来辅助定位 List<WebElement> lw =driver.findElements(By.tagName( ...
- Unity 游戏框架搭建 2019 (三十二、三十三) 类的命名 & 代码文件命名
昨天我们完成了第八个示例的第二个 MenuItem 菜单顺序的调整. 我们今天再往下接着调整. 我们来看下接下来的 MenuItem 代码如下: [MenuItem("QFramework/ ...
- 【FishFX】花式撩骚,打造TypeScript易用框架。
· 栗子入手 假设有以下foo数组,数组中每个对象都拥有id,name两个属性,现在需要查找id > 0的对象数量. const foo: Array<{ id: number, name ...
- 2019-2020-1 20199308《Linux内核原理与分析》第八周作业
<Linux内核分析> 第七章 可执行程序工作原理 7.1 知识点 1.目标文件:编译器生成的文件,"目标"指平台,它决定了编译器使用的机器指令集. 2.目标文件格式: ...
- CG-CTF(6)
CG-CTF https://cgctf.nuptsast.com/challenges#Web 续上~ 第三十一题:综合题2 查看本CMS说明: 分析: ①数据库表名为admin:字段名为usern ...
- zookeeper笔记(二)
title: zookeeper笔记(二) zookeeper ALC权限控制 getAcl path 可以查看某个node的权限 设置权限: 2. world方式 setAcl <path&g ...
- 使用IBM Blockchain Platform extension开发你的第一个fabric智能合约
文章目录 安装IBM Blockchain Platform extension for VS Code 创建一个智能合约项目 理解智能合约 打包智能合约 Local Fabric Ops 安装智能合 ...
- HTML入门——互动式推送初尝试
0.背景 疫情原因,导致许多大众喜闻乐见的体育活动停摆,但博主和队友们运营的体育社团公众号不能停摆.为了利用当下线上活动频率高的契机增加关注量,加之微信推送的互动性已成为趋势,博主打算和队友们尝试实现 ...