/**
* 对于技术面试,你还在死记硬背么?
* 快来“播”沙糖橘吧,
* 用视频案例为你实战解读技术难点
* 聚焦Java技术疑难点,实战视频为你答疑解惑
* 越“播”越甜的沙糖橘,期待你的到来和参与
* 更多详情,
* 请关注公众号沙糖橘:shatangju8801
* 请留意QQ交流群:798470346
*/
--------------------------------------------------------------------------------------------------------------------------------
动态规划问题是一系类的问题,这一系列问题核心代表是背包问题,此外还有矩阵最小路径和、最长公共子序列问题、最长上升子序列问题、台阶问题(斐波那契数列及扩展)等等。
本文选取背包问题作为动态规划思想的讲解案例。关于背包问题的概念,先看一下百度百科和维基百科的相关介绍。
百度百科:
背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由R.Merkle和M.Hellman出的。
背包问题已经研究了一个多世纪,早期的作品可追溯到1897年数学家托比亚斯·丹齐格(Tobias Dantzig,1884-1956)的早期作品 ,并指的是包装你最有价值或有用的物品而不会超载你的行李的常见问题。
维基百科:
根据维基百科,背包问题(Knapsack problem)是一种组合优化的NP完全(NP-Complete,NPC)问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。NPC问题是没有多项式时间复杂度的解法的,但是利用动态规划,我们可以以伪多项式时间复杂度求解背包问题。一般来讲,背包问题有以下几种分类:
0-1背包问题
完全背包问题
多重背包问题
此外,还存在一些其他的算法要求,例如恰好装满、求方案总数、求所有的方案等。
1.1 动态规划类问题整体思路
根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编程实现。
1.2 动态规划的原理
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写结果二维表把所有已经解决的子问题答案纪录下来,这样在解决下一个新问题时需要用到前面子问题的结果,这样,每一步计算的结构都是在上一步的结果基础之上完成,这样就可以保证所有的子问题结果都满足最优性原理。用动态规划解决问题的核心就在于填写结果二维表,二维表填写完毕,最优解也就找到了。
最优性原理是动态规划的基础,最优性原理定义:“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。也就是说,每一次子问题的尝试解决都是在之前的已经解决的子问题的基础之上进行;这样就保证了每一次子问题解决都是最优结果。【每一步子问题的解决都保证了最优】
1.3 背包问题的解决过程
在解决问题之前,为描述方便,首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,二维数组V(i,j)表示在当前背包可用容量为j的前提下,从前 i 个物品中选出的最佳组合对应的最值价值。另外定义变量Xi表示第i个物品的选择结果,这里Xi取值为0或1表示第i个物品选或者不选【这里以0-1背包为例】。解题过程如下:
(1) 建立模型,即求max(V1X1+V2X2+…+VnXn);
(2) 寻找约束条件,W1X1+W2X2+…+WnXn
(3) 寻找递推关系式,面对当前商品有两种可能性:
当前包的可用容量j比该物品i体积小,装不下物品i,此时的可获取的最大价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
还有足够的容量j可以装该物品i,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。[此处以0-1背包问题的表达式为例]
解题思路总结:
假定背包的容量为W,m是可选物品的总数,从0-V(m)依次遍历容量W,对于每一件物品i,查看当前背包的可用容量对于物品i的选择:装入背包或者不装入背包;
注意:每一次遍历假定当前的背包都是空的且当前的背包可用容量为j时(j的取值为0....W),对于物品i,有两种选择:装入还是不装入【0-1背包为例】;如果不装入物品i则需要查看上次的最优解,即从物品i-1中选择可装入背包可用容量为j时的物品的最优解,也就是从上一次子问题中选择最优解。这样依次类推,直到所有的物品选择完毕,即可得到最终的最优解。
1.4 动态规划算法思想
基本思想
动态规划和分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,动态规划法中分解得到的子问题不是互相独立的。若用分治法来解这类子问题,分解得到的子问题数目非常多,最后解决原问题需要耗费指数量级的时间。但是这些子问题有很多是相同的,也就是同一个子问题被计算了很多次,不同子问题的数量可能只有多项式量级。
如果保存已经解决的子问题的解,需要再次求解相同子问题时,直接找出已经计算出的解,这样可以减少大量重复计算,最终得到多项式时间算法。
经常用一个表【二维表】来记录所有已解决的子问题的解。不管该子问题以后是否被利用,只要它被计算过,就将其结果填入表中。这就是动态规划的基本思想。具体的动态规划算法多种多样,但它们具有相同的填表格式。【二维表可以空间优化为一维表】
基本要素
(1)最优子结构性质
(2)重叠子问题性质(子问题不是互相独立的)
设计动态规划算法的步骤
(1)问题具有最优子结构性质;
(2)构造最优值的递归关系表达式;
(3)最优值的算法描述;
(4)构造最优解;
1.5 扩展:分治法的基本思想
基本思想
分治法的设计思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
(1)将求解的较大规模问题分割成k个更小规模的子问题
(2)对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归下去,直到问题的规模足够小,很容易求出其解为止。
(3)将求出的小规模的问题的解合并成一个更大规模问题的解,自底向上逐步求出原来的解。
适用条件
(1)分治法所能解决的问题一般具有以下几个特征:
(2)该问题的规模缩小到一定的程度就可以很容易地解决;
(3)该问题可以分解为若干个规模较小的相同的问题,即该问题具有最优子结构性质;
(4)利用该问题分解出的子问题的解可以合并为该问题的解;
(5)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
动态规划和分治法的区别
分治法的子问题域是相对独立的;
动态规划的子问题域之间不是相对独立,后面求解的子问题域依赖于前面求解的子问题域的解,具有重叠包含关系。
---------------------------------------------
对应视频请移步公众号:shatangju8801
提供全系列的视频讲解。