文章目录

  • 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
一维数组变化过程如下:

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

  1. 动态规划算法 dynamic programming

    在大三上算法设计课程的时候,开始接触了动态规划,初学的时候没什么感觉,唯一的印象就是这种方法能逐步根据最优子结构得到最终的最优解.也就是保证每一个子问题的解决得到的都是最优解,那最终得到的答案肯定也是 ...

  2. 动态规划(dynamic programming)基础【背包问题】

    目   录 哔哩哔哩网站--动态规划基础 背包问题(1) 01背包 哔哩哔哩网站--[动态规划]背包问题 百度百科--背包问题 哔哩哔哩网站--动态规划基础 背包问题(1) 01背包 视频网址--哔哩 ...

  3. matlab实现k-l算法,Matlab实现动态规划算法 (dynamic programming algorithm)

    function [p_opt,fval]=dynprog(x,DecisFun,ObjFun,TransFun) % [p_opt,fval]=dynprog(x,DecisFun,ObjFun,T ...

  4. 数据结构与算法(C++)– 动态规划(Dynamic Programming)

    动态规划(Dynamic Programming) 理解动态规划的好文:https://www.sohu.com/a/153858619_466939 1.基础 **定义:**递归算法经常会有一些重复 ...

  5. 一道有关动态规划(Dynamic Programming)的网易面试题

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:机器学习算法实验室 最近遇到一道很经典的有关动态规划的网 ...

  6. 动态规划(Dynamic Programming)例题步骤详解

    文章目录 动态规划(Dynamic Programming)浅学 - 学习笔记 题目特点: 1.选择硬币组合问题:(Coin Change) 动态规划题四个核心步骤: 一.确定状态 二.转移方程 三. ...

  7. 动态规划(Dynamic Programming, DP)简介

    动态规划(Dynamic programming,DP)是一种在数学.计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法. 动态规划常常适用于有重叠子问题和最优子结 ...

  8. 强化学习(二)- 动态规划(Dynamic Programming)

    3.动态规划 3.1 介绍 术语动态规划(DP:Dynamic Programming) 指的是一个算法集合,可以用来计算最优策略,给定一个完美的环境模型,作为马尔可夫决策过程(MDP).经典的DP算 ...

  9. LeetCode 动态规划(Dynamic programming)系列题目--C++,Python解法

    LeetCode上有许多态规划(Dynamic programming)的题目,我在这里整合一下 本文章不再更新,请看LeetCode 所有题目总结 LeetCode 所有题目总结:LeetCode ...

  10. 卡塔兰数(Catalan Number)--动态规划(Dynamic Programming)

    -卡塔兰数是组合数学中一个常在各种计数问题中出现的数列.以比利时的数学家欧仁·查理·卡特兰(1814–1894)命名.历史上,清朝数学家明安图(1692年-1763年)在其<割圜密率捷法> ...

最新文章

  1. 简单安装与使用composer
  2. 2009 Competition Highlights by ICPC Live
  3. oppo手机显示服务器繁忙,如何玩转OPPO R11,五大隐藏功能你必须了解
  4. 电子商务就是计算机技术在传统商务中的应用,数据计算机论文,关于计算机Web数据其在电子商务中的应用相关参考文献资料-免费论文范文...
  5. PS教程第九课:背景色
  6. java多线程中出现的异常分别有哪些_java多线程试题
  7. 给笔记本更换SSD硬盘
  8. WEB安全第六篇--千里之外奇袭客户端:XSS和HTML注入
  9. 详细又简单的Unity的下载安装教程
  10. lol更新显示正在连接服务器,wegame更新游戏显示正在连接服务器
  11. php视频格式转换mp4教程,PHP+FFMPEG实现将视频自动转码成H264标准Mp4文件
  12. hadoop 8088端口网页无法打开
  13. C#注册机与绑定软件(转发自:韩兆新的博客园的C#学习笔记——软件注册与注册机)...
  14. doris 动态分区
  15. 【概率论与数理统计】-排列组合笔试题汇总
  16. redis基数树rax源码分析(1)
  17. word文档被覆盖了怎么恢复原状
  18. Atitit外包优缺点 提升开发效率 外包模式 1.一般来说外包优点 1.1.更加方便快捷 时间成本降低了 1.2.会导致 经济成本高,,时间成本降低了, 2.缺点 2.1.成本高 2.2.
  19. 颠覆IoT行业的开发神器!涂鸦智能重磅推出TuyaOS操作系统【程序员必备】
  20. 第九节 PyQt5之QRadioButton对象(单选按钮)

热门文章

  1. 2018-11-01 专栏一岁了-我为什么投身于普及用中文编程
  2. 剑指Offer题解(Python版)
  3. 在 Mac 上通过 Docker 运行 Asp.net Core 简易教程
  4. 第37课 thinkphp5添加商品基本信息及通过前置钩子上传商品主图 模型事件(勾子函数)...
  5. sort和qsort函数
  6. 2017年9月27日日志
  7. Android系统驱动【转】
  8. 字符数组和strcpy
  9. android--仿网易新闻主界面
  10. SQL SERVER PIVOT 行转列、列传行