动态规划算法(Dynamic Programming)之0-1背包问题

文章目录

    • 1. 问题引入
    • 2. 动态规划求解0-1背包
    • 3. 复杂度
    • 4. 0-1背包升级版(带价值)
    • 5. 0-1背包升级版(带价值)DP解法

1. 问题引入

前面讲了0-1背包的回溯解决方法,它是穷举所有可能,复杂度是指数级别的,如何降低时间复杂度呢?

对于一组不同重量、不可分割的物品,我们需要选择一些装入背包,在满足背包最大重量限制的前提下,背包中物品总重量的最大值是多少呢?

假设背包的最大承载重量是9。有5个不同的物品,重量分别是2,2,4,6,3。把回溯求解过程,用递归树画出来,就是下面这个样子:
在这里插入图片描述
从上面图中可以看出有些函数被重复计算(之前递归中也讲到了),我们希望当计算到已经计算过的函数时,不要重复计算,直接拿来用,避免重复劳动。

还是前面的0-1背包问题,分别添加和不添加重复计算判断语句,查看效果

#include <iostream>
#define MaxWeight 9   //背包承载极限
using namespace std;
bool mem [5][9];
int counttimes;
void fill(int i, int curWeight, int *bag, int N, int &maxweightinbag)
{cout << "调用次数: " << ++counttimes << endl;if(curWeight == MaxWeight || i == N)//到达极限了,或者考察完所有物品了{if(curWeight > maxweightinbag)maxweightinbag = curWeight;//记录历史最大装载量return;}//-----注释掉以下3行查看效果-------if(mem[i][curWeight])return;mem[i][curWeight] = true;//---------------------------------fill(i+1,curWeight,bag,N,maxweightinbag);//不选择当前i物品,cw不更新if(curWeight+bag[i] <= MaxWeight)//选择当前i物品,cw更新{//没有达到极限,继续装fill(i+1,curWeight+bag[i],bag,N,maxweightinbag);}
}
int main()
{const int N = 5;int bag[N] = {2,2,4,6,3};int maxweightinbag = 0;fill(0,0,bag,N,maxweightinbag);cout << "最大可装进背包的重量是:" << maxweightinbag;return 0;
}

在这里插入图片描述
在这里插入图片描述

2. 动态规划求解0-1背包

  • 把整个求解过程分为n个阶段,每个阶段会决策一个物品是否放到背包中。每个物品决策(放入或者不放入背包)完之后,背包中的物品的重量会有多种情况,也就是说,背包会达到多种不同的状态,对应到递归树中,就是有很多不同的节点。
  • 把每一层重复的状态(节点)合并只记录不同的状态,然后基于上一层的状态集合,来推导下一层的状态集合。我们可以通过合并每一层重复的状态,这样就保证每一层不同状态的个数都不会超过MaxWeight个(MaxWeight表示背包的承载极限)。于是,我们就成功避免了每层状态个数的指数级增长。
  • 用一个二维数组states[N][MaxWeight+1], 来记录每层可以达到的不同状态
bag[N] = {2,2,4,6,3}; MaxWeight = 9
  • 第0个(下标从0开始编号)物品的重量是2,要么装,要么不装,决策完之后,会对应背包的两种状态,背包中物品的总重量是0或者2。我们用states[0][0]=true和states[0][2]=true 来表示这两种状态。
    在这里插入图片描述
  • 第1个物品的重量也是2,基于之前的背包状态,在这个物品决策完之后,不同的状态有3个,背包中物品总重量分别是0(0+0),2(0+2 or 2+0),4(2+2)。我们用states[1][0]=true,states[1][2]=true,states[1][4]=true来表示这三种状态。
    在这里插入图片描述
  • 只需要在最后一层,找一个值为true的最接近 MaxWeight(这里是9)的值,就是背包中物品总重量的最大值。
/*** @description: 0-1背包--dp应用* @author: michael ming* @date: 2019/7/9 1:13* @modified by: */
#include <iostream>
#define MaxWeight 9   //背包承载极限
const int N = 5;    //背包个数
using namespace std;
bool states[N][MaxWeight+1];//全局参数自动初始化,默认是false
int fill_dp(int *bag, int N)
{states[0][0] = true;//第1个背包不放if(bag[0] <= MaxWeight)states[0][bag[0]] = true;//第1个背包放for(int i = 1; i < N; ++i)//动态规划状态转移{for(int j = 0; j <= MaxWeight; ++j)//不把第i个物品放入背包{if(states[i-1][j] == true)states[i][j] = states[i-1][j];//把上一行的状态复制下来(i不放物品)}for(int j = 0; j+bag[i] <= MaxWeight; ++j)if(states[i-1][j] == true)states[i][j+bag[i]] = true;//把第i个物品放入背包}for(int i = MaxWeight; i >= 0; --i)//把最后一行,从后往前找最重的背包{if(states[N-1][i] == true)return i;//最大重量}return 0;
}
int main()
{int bag[N] = {2,2,4,6,3};cout << "最大可装进背包的重量是:" << fill_dp(bag,N);return 0;
}

在这里插入图片描述

3. 复杂度

上面就是一种用动态规划解决问题的思路。把问题分解为多个阶段,每个阶段对应一个决策。记录每一个阶段可达的状态集合(去掉重复的),然后通过当前的状态集合,来推导下一阶段的状态集合,动态地往前推进。

  • 用回溯算法解决这个问题的时间复杂度O(2n),是指数级的。

  • 上面DP代码耗时最多的部分是代码中的两层for循环,所以时间复杂度是O(N * MaxWeight)。N表示物品个数,MaxWeight表示背包承载极限。

  • 尽管动态规划的执行效率比较高但是就上面DP代码实现来说,我们需要额外申请一个N*(MaxWeight+1)的二维数组,对空间的消耗比较多。所以,动态规划是一种空间换时间的解决思路。有什么办法可以降低空间消耗吗?

  • 实际上,我们只需要一个大小为 MaxWeight+1 的一维数组就可以解决这个问题。动态规划状态转移的过程,都可以基于这个一维数组来操作。具体的代码如下。

/*** @description: * @author: michael ming* @date: 2019/7/15 22:00* @modified by: */
#include <iostream>
#define MaxWeight 9   //背包承载极限
const int N = 5;    //背包个数
using namespace std;
bool states[MaxWeight+1];//全局参数自动初始化,默认是false
int fill_dp(int *bag, int N)
{states[0] = true;//第1个背包不放if(bag[0] <= MaxWeight)states[bag[0]] = true;//第1个背包放for(int i = 1; i < N; ++i)//动态规划状态转移{for(int j = MaxWeight-bag[i]; j >= 0; --j)//把第i个物品放入背包{if(states[j] == true)states[j+bag[i]] = true;}}for(int i = MaxWeight; i >= 0; --i)//输出结果{if(states[i] == true)return i;//最大重量}return 0;
}
int main()
{int bag[N] = {2,2,4,6,3};cout << "最大可装进背包的重量是:" << fill_dp(bag,N);return 0;
}

内层for循环,j 需要从大到小处理,如果从小到大,会出现重复计算
在这里插入图片描述

4. 0-1背包升级版(带价值)

每个物品对应着一种价值,不超过背包载重极限,求可装入背包的最大总价值。

//--------回溯解法-------------------
int maxV = -1; // 最大价值放到 maxV 中
int weight[5] = {2,2,4,6,3};  // 物品的重量
int value[5] = {3,4,8,9,6}; // 物品的价值
int N = 5; // 物品个数
int MaxWeight = 9; // 背包承受的最大重量
void f(int i, int cw, int cv) 
{ // 调用 f(0, 0, 0)if (cw == MaxWeight || i == N) { // cw==MaxWeight 表示装满了,i==N 表示物品都考察完了if (cv > maxV) maxV = cv;return;}f(i+1, cw, cv); // 选择不装第 i 个物品if (cw + weight[i] <= w) {f(i+1, cw+weight[i], cv+value[i]); // 选择装第 i 个物品}
}

对上面代码画出递归树,每个节点表示一个状态。现在我们需要3个变量(i,cw,cv)来表示一个状态。其中,i表示即将要决策第i个物品是否装入背包,cw表示当前背包中物品的总重量,cv表示当前背包中物品的总价值。
在这里插入图片描述

  • 我们发现,在递归树有几个节点的 i 和 cw 是完全相同的,比如(2,2,4)和(2,2,3)。
  • 在背包中物品总重量一样的情况下,f(2,2,4)这种状态的物品总价值更大,可以舍弃 f(2,2,3)这种状态,只需要沿着 f(2,2,4)这条决策路线继续往下决策就可以。
  • 也就是,对于(i,cw)相同的不同状态,只需要保留 cv 值最大的那个,继续递归处理,其他状态不予考虑。

5. 0-1背包升级版(带价值)DP解法

  • 把整个求解过程分为n个阶段,每个阶段会决策一个物品是否放到背包中。
  • 每个阶段决策完之后,背包中的物品的总重量以及总价值,会有多种情况。
  • 用一个二维数组 states[N][MaxWeight+1],来记录每层可以达到的不同状态。
  • 这里数组存储的值不再是bool类型的了,而是当前状态对应的最大总价值。
  • 把每一层中(i,cw)重复的状态(节点)合并,只记录cv值最大的那个状态,然后基于这些状态来推导下一层的状态。
/*** @description: 0-1背包带价值,dp解法* @author: michael ming* @date: 2019/7/16 0:07* @modified by: */
#include <iostream>
#define MaxWeight 9   //背包承载极限
const int N = 5;    //背包个数
using namespace std;
int fill_value_dp(int* weight, int* value, int N)
{int (*states) [MaxWeight+1]  = new int [N][MaxWeight+1];for (int i = 0; i < N; ++i) // 初始化 states{for (int j = 0; j < MaxWeight+1; ++j)states[i][j] = -1;}states[0][0] = 0;//第一个不放,价值0存入statesif (weight[0] <= MaxWeight){states[0][weight[0]] = value[0];//第一个放入背包}for (int i = 1; i < N; ++i) // 动态规划,状态转移{for (int j = 0; j <= MaxWeight; ++j){ // 不选择第 i 个物品if (states[i-1][j] >= 0)states[i][j] = states[i-1][j];//直接复制上一层的状态}for (int j = 0; j+weight[i] <= MaxWeight; ++j){ // 选择第 i 个物品if (states[i-1][j] >= 0){int v = states[i-1][j] + value[i];if (v > states[i][j+weight[i]]){//只存价值最大的states[i][j+weight[i]] = v;}}}}// 找出最大值int maxvalue = -1;// 最大价值放到 maxvalue 中for (int j = 0; j <= MaxWeight; ++j){if (states[N-1][j] > maxvalue)maxvalue = states[N-1][j];}delete [] states;return maxvalue;
}
int main()
{int weight[5] = {2,2,4,6,3};  // 物品的重量int value[5] = {3,4,8,9,6}; // 物品的价值cout << "最大可装进背包的价值是:" << fill_value_dp(weight,value,N);return 0;
}

时间和空间复杂度都是O(N * MaxWeight)
在这里插入图片描述
在这里插入图片描述
使用一维数组也可DP解题,空间复杂度减小为O(MaxWeight)

/*** @description: 0-1背包带价值,dp解法(状态存储用一维数组)* @author: michael ming* @date: 2019/7/16 22:17* @modified by: */
#include <iostream>
#define MaxWeight 9   //背包承载极限
const int N = 5;    //背包个数
using namespace std;
int fill_value_dp(int* weight, int* value, int N)
{int *states  = new int [MaxWeight+1];for (int i = 0; i < MaxWeight+1; ++i) // 初始化 states{states[i] = -1;}states[0] = 0;//第一个不放,价值0存入statesif (weight[0] <= MaxWeight){states[weight[0]] = value[0];//第一个放入背包}for (int i = 1; i < N; ++i) // 动态规划,状态转移{for (int j = MaxWeight-weight[i]; j >= 0; --j)
//        for (int j = 0; j <= MaxWeight-weight[i]; ++j){ // 选择第 i 个物品if (states[j] >= 0){int v = states[j] + value[i];if (v > states[j+weight[i]]){//只存价值最大的states[j+weight[i]] = v;}}}}// 找出最大值int maxvalue = -1;// 最大价值放到 maxvalue 中for (int i = 0; i <= MaxWeight; ++i){if (states[i] > maxvalue)maxvalue = states[i];}delete [] states;return maxvalue;
}
int main()
{int weight[N] = {2,2,4,6,3};  // 物品的重量int value[N] = {3,4,8,9,6}; // 物品的价值cout << "最大可装进背包的价值是:" << fill_value_dp(weight,value,N);return 0;
}

最大可装进背包的价值是:18
一维数组变化过程如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/480101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java多线程系列(一):最全面的Java多线程学习概述

Java并发编程的技能基本涵括以下5方面&#xff1a; 多线程 线程池 线程锁 并发工具类 并发容器 多线程的4种创建方式 继承Thread 实现Runnable接口 实现Callable接口 以及线程池来创建线程 Java线程的生命周期 线程的创建 线程的就绪 线程的运行 线程的阻塞 线程的死…

论文浅尝 | 基于知识库的自然语言理解 01#

本文转载自公众号&#xff1a;知识工场。罗康琦&#xff0c;上海交通大学计算机系2019届博士&#xff0c;研究方向为自然语义理解和知识图谱。2012年获得华中科技大学软件工程学士学位&#xff0c;现就职于京东数据科学实验室&#xff08;Data Science Lab&#xff09;。他曾在…

推荐系统图算法实用干货汇总(含论文、代码、样例教程)

Hello,今天为朋友们推荐一个值得学习的公众号「浅梦的学习笔记」&#xff0c;小编目前在电商公司从事推荐系统相关的工作。主要分享关于「推荐系统&#xff0c;图算法&#xff0c;NLP&CV以及求职面试」等内容&#xff0c;欢迎关注一起学习和交流&#xff5e;文末可打包下载…

动态规划应用--双11购物凑单

文章目录1. 问题描述2. 代码实现1. 问题描述 双11购物节的时候&#xff0c;某宝给你很多张满300减50的优惠券&#xff0c;你想组合各种商品的价格总和>300&#xff0c;且金额总和越接近300越好&#xff0c;这样可以多薅点羊毛。 回溯算法效率太低&#xff0c;时间复杂度指…

商汤科技2020数据分析师0820笔试题目整理

2019年8月19日 问答题1&#xff1a;缺失值数据预处理有哪些方法&#xff1f;https://juejin.im/post/5b5c4e6c6fb9a04f90791e0c 处理缺失值的方法如下&#xff1a;删除记录&#xff0c;数据填补和不处理。主要以数据填补为主。 1 删除记录&#xff1a;该种方法在样本数据量十分…

Java多线程系列(八):ConcurrentHashMap的实现原理(JDK1.7和JDK1.8)

HashMap、CurrentHashMap 的实现原理基本都是BAT面试必考内容&#xff0c;阿里P8架构师谈&#xff1a;深入探讨HashMap的底层结构、原理、扩容机制深入谈过hashmap的实现原理以及在JDK 1.8的实现区别&#xff0c;今天主要谈CurrentHashMap的实现原理&#xff0c;以及在JDK1.7和…

论文笔记(Neural Graph Collaborative Filtering)

神经图协同过滤 论文链接&#xff1a;Neural Graph Collaborative Filtering, SIGIR’19 原理&#xff1a;在 user-item interaction graph 上使用 GNN 来学习 user 向量和item 向量&#xff0c;用户向量和项向量的内积来预测评分。 区别&#xff1a; 大部分论文使用 GNN 只是…

论文浅尝 | 基于知识库的自然语言理解 02#

本文转载自公众号&#xff1a;知识工场。罗康琦&#xff0c;上海交通大学计算机系2019届博士&#xff0c;研究方向为自然语义理解和知识图谱。2012年获得华中科技大学软件工程学士学位&#xff0c;现就职于京东数据科学实验室&#xff08;Data Science Lab&#xff09;。他曾在…

工业解密:百度地图背后的路线时长预估模型!

星标/置顶小屋&#xff0c;带你解锁最萌最前沿的NLP、搜索与推荐技术编 | YY无论你是苦逼学生&#xff08;公交地铁狗&#xff09;、职场萌新&#xff08;打车狗&#xff09;还是有钱大佬&#xff08;有车一族&#xff09;&#xff0c;只要站在了北上广深的土地上&#xff0c;就…

动态规划应用--“杨辉三角”最短路径 LeetCode 120

文章目录1. 问题描述2. DP算法代码3. LeetCode 120 三角形最小路径和1. 问题描述 对“杨辉三角"进行一些改造。每个位置的数字可以随意填写&#xff0c;经过某个数字只能到达下面一层相邻的两个数字。 假设你站在第一层&#xff0c;往下移动&#xff0c;我们把移动到最底…

Java多线程系列(九):CountDownLatch、Semaphore等4大并发工具类详解

之前谈过高并发编程系列&#xff1a;4种常用Java线程锁的特点&#xff0c;性能比较、使用场景 &#xff0c;以及高并发编程系列&#xff1a;ConcurrentHashMap的实现原理(JDK1.7和JDK1.8) 今天主要介绍concurrent包的内容以及4大并发工具类。 Java并发工具包 1.并发工具类 …

快手 算法工程师 0825 笔试题

4 求解一元一次方程的解 #include <cstdio> #include <iostream> #include <string.h> #include <cstring> #include <algorithm> using namespace std; int x0,n0,xr0,nr0; int flag 0; void Adds(string a,char op) { if(flag0) {if(a…

论文笔记(Neural Collaborative Filtering)

神经协同过滤 论文链接&#xff1a;Neural Collaborative Filtering, WWW’17 原理&#xff1a;融合 GMF 和 MLP 1. 摘要 虽然最近的一些研究使用深度学习作为推荐&#xff0c;但他们主要是用深度学习来建模辅助信息&#xff0c;例如 item 的文本描述。在表示协同过滤的关键…

玩转算法第七章-二叉树与递归

二叉树与递归 二叉树的前序遍历 leetcode 104 将两个递归函数映射到max函数中去 思考题&#xff1a;leetcode 111 leetcode&#xff1a;226 扩展题&#xff1a;leetcode 100 leetcode&#xff1a;101 第一个是&#xff0c;第二个不是 leetcode 222&#xff1a; leet…

POJ 2965 开冰箱的门(回溯)

文章目录1. 题目1.1 题目链接1.2 题目大意1.3 解题思路2. 代码2.1 Accepted代码1. 题目 1.1 题目链接 http://poj.org/problem?id2965 1.2 题目大意 有一个4*4的符号矩阵&#xff08;和-&#xff09;&#xff0c;改变一个元素的符号&#xff0c;它所在的行和列的其他元素也…

论文浅尝 | 基于知识库的自然语言理解 03#

本文转载自公众号: 知识工场。罗康琦&#xff0c;上海交通大学计算机系2019届博士&#xff0c;研究方向为自然语义理解和知识图谱。2012年获得华中科技大学软件工程学士学位&#xff0c;现就职于京东数据科学实验室&#xff08;Data Science Lab&#xff09;。他曾在AAAI&#…

Java多线程系列(七):并发容器的原理,7大并发容器详解、及使用场景

之前谈过高并发编程系列&#xff1a; 高并发编程系列&#xff1a;4种常用Java线程锁的特点&#xff0c;性能比较、使用场景 高并发编程系列&#xff1a;CountDownLatch、Semaphore等4大并发工具类详解 高并发编程系列&#xff1a;4大JVM性能分析工具详解&#xff0c;及内存…

MSRA提出通用文档预训练模型LayoutLM,通往文档智能之路!

星标/置顶小屋&#xff0c;带你解锁最萌最前沿的NLP、搜索与推荐技术随着数字化进程的加快&#xff0c;文档、图像等载体的结构化分析和内容提取成为关乎企业数字化转型成败的关键一环&#xff0c;自动、精准、快速的信息处理对于生产力的提升至关重要。以商业文档为例&#xf…

tensorflow--GPU

一、查看 tensorflow 是否使用了GPU进行计算 import tensorflow as tf sess tf.Session(configtf.ConfigProto(log_device_placementTrue)) 运行程序&#xff0c;日志若包含 gpu 信息&#xff0c;则使用了 gpu。 二、使用指定GPU 方式一&#xff1a;代码&#xff1a; imp…

玩转算法之面试 第八章-递归与回溯

树形问题 leetcode&#xff1a;17 1 字符串的合法性 &#xff08;是否包括1&#xff0c;*和#号键&#xff09; 2 空字符串 3 多个解的顺序 部分源代码如下&#xff1a; 在这里插入代码片 #include<cstring>using namespace std;private:const string letterMap[1…