动态规划(Dynamic Programming,DP,P指的是一种表格法,不是编程,而是一种表格处理方法,把每一步得到的子问题结果存储在表格中,每次遇到该子问题时不需要再求解一遍,只需要查询表格即可),由美国数学家理查德.贝尔曼(richard bellman)发明。
算法原理
动态规划算法的核心就是记住已经解决过的子问题的解。动态规划法建议,与其对交叠的子问题一次又一次地求解,不如对每个较小的子问题只求解一次,并把结果记录在表中。
基本思想与分治法类似,也是将待求的问题分解为若干个子问题,按顺序求子问题的解。但分治法产生的子问题之间相互独立,而动态规划算法产生的子问题之间不是相互独立的,不同的子问题具有公共的子问题(问题由交叠的子问题构成),前一子问题的解,为后一子问题提供有用信息,每次决策依赖于当前状态,又随即引起状态的转移。
动态规划就是将原问题分解为若干子问题,然后自底向上,先求解最小的子问题,把结果存储在表格中,再求解最大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。
首先看一下斐波那契数列的求解过程,python语言实现代码为
def fib(n):
ifn==0:
return 0
ifn==1:
return 1
return fib(n-1)+fib(n-2)
print fib(6)
先来分析一下递归分治算法的执行流程,假如输入6,那么执行的递归树如下:
上面的递归树中将fib(6)问题分解为fib(1)—fib(5)众多子问题,有些问题是其他子问题的公共子问题,如fib(2)是fib(4)、fib(3)的公共子问题,但因为所有的子问题都会被执行,这样很多重复的节点(公共子子问题)被执行,如fib(2)被重复执行了5次。由于调用每一个函数的时候都要保留上下文,所以空间上开销也不小。如果在执行的时候把执行过的子节点保存起来,后面要用到的时候直接查表调用的话可以节约大量的时间。动态规划法就是对每个子子问题只求解一次,将其保存在一个表格中,从而无需每次求解一个子问题时都重新计算。
这也就是动态规划的核心,先计算子问题,再由子问题计算父问题。
def fibd(n):
Mem=[None]*7
Mem[0]=0
Mem[1]=1
for i in range(2,n+1):
Mem[i]=Mem[i-1]+Mem[i-2]
return Mem[n]
print fibd(6)
算法使用场景
动态规划法通常用来求解最优化问题,对于一个问题,我们希望寻找到一个最优解,而不是最优解,因为可能有多个解都达到最优解。动态规划适用于两类问题,问题中包含重叠子问题和最优子结构。
所谓重叠子问题,如果递归算法反复求解相同的子问题,就称为具有重叠子问题(overlapping subproblems)性质,先计算子问题的解,再由子问题的解去构造问题的解。由于子问题存在重叠,在动态规划算法中使用(一维或二维)数组来保存子问题的解,这样子问题多次求解的时候可以直接查表不用调用函数递归。如在斐波拉契数列中,可以看到大量的重叠子问题,比如说在求fib(6)的时候,fib(2)被调用了5次。使用递归算法的时候会反复的求解相同的子问题,不停的调用函数,而不是生成新的子问题。
所谓最优子结构(optimal-substructure),如果一个问题的最优解解结构由其子问题的最优解构成,就称此问题具有最优子结构性质。最优解包含了其子问题的最优解,不是合并所有子问题的解,而是找最优的一条解线路,选择部分子最优解来达到最终的最优解。它是使用动态规划的最基本条件。理查德.贝尔曼称其为最优化法则(principle of optimality)。
因此,只要满足最优子结构性质就可以使用动态规划,如果还具有子问题重叠,则更能彰显动态规划的优势。
动态规划法的解题步骤
1、 将原问题分解为子问题(子问题和原问题形式相同,且子问题解求出就会被保存),分析其特征,证明原问题具有最优子结构特征(原问题的最优解包含其子问题的最优解)
2、 建立最优解的递归式(状态转移方程),从上一个状态到下一个状态之间可能存在一些变化,这个变化所表示的表达式, 状态转移方程法有点类似递归的解题思路。我们需要分析,某个问题如何通过子问题来递归求解,写出递归公式,也就是所谓的状态转移方程。
3、自底向上计算最优值,并记录
4、利用计算出的信息,构造一个最优解