前言

众所周知,递归算法时间复杂度很高为(2^n),而动态规划算法也能够解决此类问题,动态规划的算法的时间复杂度为(n^2)。动态规划算法是以空间置换时间的解决方式。

一、什么是动态规划

动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

动态规划对于子问题重叠的情况特别有效,因为它将子问题的解保存在存储空间中,当需要某个子问题的解时,直接取值即可,从而避免重复计算!

二、基本策略

基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

动态规划中的子问题往往不是相互独立的(即子问题重叠)。在求解的过程中,许多子问题的解被反复地使用。为了避免重复计算,动态规划算法采用了填表来保存子问题解的方法。

三、什么问题适合用动态规划来解决呢?

适合用动态规划来解决的问题,都具有下面两个特点:最优子结构、重叠子问题。

如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。某阶段状态(定义的新子问题)一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与其以前的状态有关。子问题之间是不独立的(分治法是独立的),一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。

如果使用递归算法的时候会反复的求解相同的子问题,不停的调用函数,而不是生成新的子问题。如果递归算法反复求解相同的子问题,就称为具有重叠子问题(overlapping subproblems)性质。在动态规划算法中使用数组来保存子问题的解,这样子问题多次求解的时候可以直接查表不用调用函数递归。

这类问题的求解步骤通常如下:

  1. 分析最优解的性质,并刻画其结构特征,这一步的开始时一定要从子问题入手;
  2. 定义最优解变量,定义递归最优解公式;
  3. 以自底向上计算出最优值(或自顶向下的记忆化方式(即备忘录法));
  4. 根据计算最优值时得到的信息,构造问题的最优解。

四、使用动态规划算法解决问题举例

1.斐波那契(Fibonacci )

前面我们使用了递归的方式去计算斐波那契,使用递归实现的最大问题是每次调用都要把上一步的结果重新计算一次,如果值很大,很容易造成内存溢出,如果值很大,那递归是不能计算出结果的。这时,我们考虑,可不可以把上一步计算的结果保存起来,那下一步就不需要再次计算了,这个思想其实就是动态规划。下面就看看动态规划的两种方法怎样来解决斐波那契数列问题。

①自顶向下的备忘录法

    public static int Fibonacci(int n) {if (n <= 0)return n;int[] Memo = new int[n + 1];for (int i = 0; i <= n; i++)Memo[i] = -1;return fib(n, Memo);}public static int fib(int n, int[] Memo) {if (Memo[n] != -1)return Memo[n];//如果已经求出了fib(n)的值直接返回,否则将求出的值保存在Memo备忘录中。               if (n <= 2)Memo[n] = 1;else Memo[n] = fib(n - 1, Memo) + fib(n - 2, Memo);return Memo[n];}

备忘录法也是比较好理解的,创建了一个n+1大小的数组来保存求出的斐波拉契数列中的每一个值,在递归的时候如果发现前面fib(n)的值计算出来了就不再计算,如果未计算出来,则计算出来后保存在Memo数组中,下次在调用fib(n)的时候就不会重新递归了。

②自底向上的动态规划

备忘录法还是利用了递归,上面算法不管怎样,计算fib(6)的时候最后还是要计算出fib(1),fib(2),fib(3)……,那么何不先计算出fib(1),fib(2),fib(3)……,呢?这也就是动态规划的核心,先计算子问题,再由子问题计算父问题。

    public static int fib(int n) {if (n <= 0)return n;int[] Memo = new int[n + 1];Memo[0] = 0;Memo[1] = 1;for (int i = 2; i <= n; i++) {Memo[i] = Memo[i - 1] + Memo[i - 2];}return Memo[n];}

自底向上方法也是利用数组保存了先计算的值,为后面的调用服务。观察参与循环的只有 i,i-1 , i-2三项,因此该方法的空间可以进一步优化如下。

    public static int fib(int n) {if (n <= 1)return n;int Memo_i_2 = 0;int Memo_i_1 = 1;int Memo_i = 1;for (int i = 2; i <= n; i++) {Memo_i = Memo_i_2 + Memo_i_1;Memo_i_2 = Memo_i_1;Memo_i_1 = Memo_i;}return Memo_i;}

一般来说由于备忘录方式的动态规划方法使用了递归,递归的时候会产生额外的开销,使用自底向上的动态规划方法要比备忘录方法好。

2.钢条切割

来自算法导论,我们只看最优解法:自底向上的动态规划

    public static int buttom_up_cut(int[] p) {int[] r = new int[p.length + 1];for (int i = 1; i <= p.length; i++) {int q = -1;//①for (int j = 1; j <= i; j++)q = Math.max(q, p[j - 1] + r[i - j]);r[i] = q;}return r[p.length];}

问题中最重要的是理解注释①处的循环,这里外面的循环是求r[1],r[2]……,里面的循环是求出r[1],r[2]……的最优解,也就是说r[i]中保存的是钢条长度为i时划分的最优解,这里面涉及到了最优子结构问题,也就是一个问题取最优解的时候,它的子问题也一定要取得最优解。下面是长度为4的钢条划分的结构图。

五、动态规划的经典模型

1.线性模型:线性模型的是动态规划中最常用的模型,上文讲到的钢条切割问题就是经典的线性模型,这里的线性指的是状态的排布是呈线性的。

2.区间模型:区间模型的状态表示一般为d[i][j],表示区间[i, j]上的最优解,然后通过状态转移计算出[i+1, j]或者[i, j+1]上的最优解,逐步扩大区间的范围,最终求得[1, len]的最优解。

3.01背包模型:参考【动态规划解决01背包问题】有N种物品(每种物品1件)和一个容量为V的背包。放入第 i 种物品耗费的空间是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。f[i][v]表示前i种物品恰好放入一个容量为v的背包可以获得的最大价值。决策为第i个物品在前i-1个物品放置完毕后,是选择放还是不放,状态转移方程为:

f[i][v] = max{ f[i-1][v], f[i-1][v – Ci] +Wi }

时间复杂度O(VN),空间复杂度O(VN) (空间复杂度可利用滚动数组进行优化达到O(V) )。

六、动态规划与分治法的区别和联系

分治法是指将问题划分成一些独立地子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。

动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。

分治法主要在于子问题的独立性,比如排序算法等, 动态规划算法主要适用于处理 子问题重复性和最优子结构的的问题。

七、总结

  1. 动态规划算法的核心就是记住已经解决过的子问题的解。
  2. 求解的方式有两种:①自顶向下的备忘录法 ②自底向上。

动态规划可以解决哪些类型的问题?

待解决的原问题较难,但此问题可以被不断拆分成一个个小问题,而小问题的解是非常容易获得的;如果单单只是利用递归的方法来解决原问题,那么采用的是分治法的思想,动态规划具有记忆性,将子问题的解都记录下来,以免在递归的过程中重复计算,从而减少了计算量。


我的微信公众号:架构真经(id:gentoo666),分享Java干货,高并发编程,热门技术教程,微服务及分布式技术,架构设计,区块链技术,人工智能,大数据,Java面试题,以及前沿热门资讯等。每日更新哦!

参考资料:

  1. https://www.cnblogs.com/lixiaochao/p/8443120.html
  2. https://www.cnblogs.com/xiaotuan/p/7260111.html
  3. https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html
  4. https://blog.csdn.net/u013309870/article/details/75193592
  5. https://www.jianshu.com/p/86daf14620b1

程序员的算法课(5)-动态规划算法相关推荐

  1. 程序员如何快速准备面试中的算法 - 结构之法

    准备面试.学习算法,特别推荐最新出版的我的新书<编程之法:面试和算法心得>,已经上架京东等各大网店 前言 我决定写篇短文,即为此文.之所以要写这篇文章,缘于微博上常有朋友询问,要毕业找工作 ...

  2. 程序员编程艺术:面试和算法心得

    本文转载至:http://taop.marchtea.com/ 本书是July和他伙伴们的<程序员编程艺术>的电子书 <程序员编程艺术:面试和算法心得> 目录 第一部分 数据结 ...

  3. 【新书速递】程序员必会的40种算法

    算法是计算科学的核心,在求解实际问题的过程中发挥着重要作用.程序员.算法设计师.架构师.数据分析师等信息技术相关从业人员都应学习算法设计基础知识,积累基础算法,掌握典型的机器学习算法.自然语言处理算法 ...

  4. 《程序员编程艺术:面试和算法心得》链接

    转载自: http://taop.marchtea.com/index.html The Art of Programming By July 本书是July和他伙伴们的<程序员编程艺术> ...

  5. 前端程序员大厂面试精选100道算法题2

    碎碎念: 亲爱的读者:你好!我的名字叫昌龙 [Changlon] -- 一个非科班程序员.一个致力于前端的开发者.一个热爱生活且又时有忧郁的思考者. 如果我的文章能给你带来一些收获,你的点赞收藏将是对 ...

  6. 程序员如何快速准备面试中的算法

    程序员如何快速准备面试中的算法 准备面试.学习算法,特别推荐最新出版的 新书<编程之法:面试和算法心得>,已经上架 京东等各大网店 前言 我决定写篇短文,即为此文.之所以要写这篇文章,缘于 ...

  7. 好程序员web前端分享javascript枚举算法

    好程序员web前端分享javascript枚举算法,题目:在1,2,3,4,5 五个数中,我们随机选取 3个数.问有多少种取法?并且把每种取出数的方法列举出来. 乍看这道题,其实感觉没什么难度.三个f ...

  8. 程序员面试需要刷力扣算法题吗

    这里写目录标题 1. 程序员面试需要刷力扣算法题吗 1.1. 算法题的一些特征 1.2. 为什么要考查算法 1.3. 目前面试主要考查 3 类 1. 程序员面试需要刷力扣算法题吗 1.1. 算法题的一 ...

  9. 2018年5月下旬值得一读的10本技术书籍(Python、程序员英语、区块链、算法等书籍)!福利见文末!

    5月下旬, 小编 为大家带来10本技术书籍(Python.程序员英语.区块链.算法等书籍).以下为书籍详情,文末还有福利哦! 书籍名称:<程序员的英语> 本书旨在最大限度提高对开发人员最重 ...

  10. 程序员必知的40个算法

    点击上方蓝色字体,关注程序员zhenguo 你好,我是zhenguo 在讲述程序员必知的40个算法前,我想拿出相当一段篇幅阐述怎样学习算法,以及算法学习切记不能怎么样做.对于每一位程序员或许都有一点用 ...

最新文章

  1. 解释一下python中的//,%和**运算符
  2. ajax.ajaxmethod无效,jQuery Ajax调用httpget webmethod(C#)无效
  3. PLSQL大数据生成规则
  4. django orm 以列表作为筛选条件进行查询
  5. 书接上文——python实现感知分类器模型分类过程动态可视化
  6. CentOS安装vim
  7. hdu 5823 color II 状压dp
  8. linux系统堆栈内存分配,Linux中堆栈内存在物理上是连续的吗?
  9. 下载网页中的html5视频之手动方法
  10. Java读取配置文件Java加载不同环境的配置文件
  11. 历史_美股和美债的联动关系
  12. Android视频列表自动播放功能
  13. IMAX Enhanced:让沉浸式家庭影音娱乐体验不再抽象
  14. c语言中ipv6地址比较大小,ipv6地址处置
  15. Unity游戏开发前置知识
  16. jspm彩虹滑板专卖网店系统毕业设计(附源码、运行环境)
  17. Revisiting Network Support for RDMA
  18. 深度学习,分割后处理之通过连通成分分析去除假阳性区域,提高分割准确度
  19. 关于Jupyter Notebook的环境配置
  20. 【SQL语法基础】了解SQL:一门半衰期很长的语言

热门文章

  1. Redis 架构之 cluster
  2. 黑客攻防技术宝典Web实战篇第2版—第10章 测试后端组件
  3. 在使用 Elasticsearch 时要注意什么?
  4. OpenStack SFC 深入剖析
  5. caffeine 弱引用key的实现
  6. ashx一般处理程序
  7. RocketMQ有哪些消息类型?springboot如何整合rocketMQ
  8. 记忆集、卡表、G1垃圾收集器简介
  9. Java 并发编程CAS、volatile、synchronized原理详解
  10. Linux内核调试的方式以及工具集锦