动态规划(Dynamic Programming)与分治方法类似,都是经过组合子问题的解来求解原问题。不一样的是,分治方法一般将问题划分为互不相交的子问题,递归地求解子问题,再讲它们的解组合起来,求出原问题的解。而动态规划应用于子问题重叠的状况,即不用的子问题具备公共的子子问题。在这种状况下,若是采用分治算法,则分治算法会作许多没必要要的工做,它会反复地求解那些公共子子问题。对于动态规划法,它对每一个子子问题只求解一次,将其保存在一个表格中,从而无需每次求解一个子子问题时都从新计算,避免了这种没必要要的计算工做。
也就是说,动态规划法与分治方法相比,是用空间来换时间,而时间上得到的效益是很客观的,这是一种典型的时空平衡(time-memory trade-off)的策略。一般,动态规划法用来求解最优化问题(optimization problem),如斐波那契数列求值问题,钢条切割问题,0-1背包问题,矩阵链乘法问题,最长公共子序列(LCS)问题,最优二叉搜索树问题等。
通常状况下,动态规划算法的步骤以下:算法
接下来,咱们将从斐波那契数列求值这个简单的例子入手,来分析动态规划法的具体步骤和优势。app
斐波那契数列记为$\{f(n)\}$,其表达式以下:函数
$$ \left\{ \begin{array}{lr} f(0)=0\\ f(1)=1\\ f(n)=f(n-1)+f(n-2),n\geq 2 \end{array} \right. $$优化
具体写出前几项,就是:0,1,1,2,3,5,8,13,21,34,55,89,144,233......
接下来,咱们将会采用递归法和动态规划法来求解该数列的第n项,即f(n)的值。spa
首先,咱们采用递归法来求解斐波那契数列的第n项$f(n)$,其算法描述以下:code
function fib(n) if n = 0 return 0 if n = 1 return 1 return fib(n − 1) + fib(n − 2)
分析上述伪代码,先是定义一个函数fib(n),用来计算斐波那契数列的第n项,当$n\geq 2$时,它的返回值会调用函数fib(n-1)和fib(n-2).当$n=5$时,计算fib(5)的函数调用状况以下图所示:blog
在计算fib(5)时,fib(5)调用1次,fib(4)调用1次,fib(3)调用2次,fib(2)调用3次,fib(1)调用5次,fib(0)调用3次,一共调用函数fib()15次。由此,咱们能够看到,在计算fib(5)时,存在屡次重复的fib()函数的调用,当n增大时,重复调用的次数会急剧增长,如计算fib(50)时,fib(1)和fib(0)大约会被调用$2.4\times10^{10}$次。因而可知,该算法的效率并非很高,由于该算法的运行时间是指数时间。
咱们用Python实现上述算法,并计算f(38)的值及运算时间。Python代码以下:递归
import time # recursive method def rec_fib(n): if n <= 1: return n else: return rec_fib(n-1) + rec_fib(n-2) # time cost of cursive method t1 = time.time() t = rec_fib(38) t2 = time.time() print('结果:%s, 运行时间:%s'%(t, t2-t1))
输出结果以下:内存
结果:39088169, 运行时间:22.93831205368042
在使用递归法来求解斐波那契数列的第n项时,咱们看到了递归法的不足之处,由于递归法在使用过程当中存在大量重复的函数调用,所以,效率不好,运行时间为指数时间。为了解决递归法存在的问题,咱们能够尝试动态规划法,由于动态规划法会在运行过程当中,保存上一个子问题的解,从而避免了重复求解子问题。对于求解斐波那契数列的第n项,咱们在使用动态规划法时,须要保存f(n-1)和f(n-2)的值,牺牲一点内存,可是能够显著地提高运行效率。
动态规划法来求解斐波那契数列第n项的伪代码以下:ci
function fib(n) var previousFib := 0, currentFib := 1 if n = 0 return 0 else if n = 1 return 1 repeat n−1 times var newFib := previousFib + currentFib previousFib := currentFib currentFib := newFib return currentFib
在上述伪代码中,并无存在重复求解问题,只是在每次运行过程当中,保存上两项的值,再利用公式$f(n)=f(n-1)+f(n-2)$来求解第n项的值。用Python实现上述过程,代码以下:
import time # bottom up approach of Dynamic Programming def dp_fib(n): previousFib = 0 currentFib = 1 if n <= 1: return n # repeat n-1 times for _ in range(n-1): newFib = previousFib + currentFib previousFib = currentFib currentFib = newFib return currentFib # time cost of DP method t1 = time.time() t = dp_fib(38) t2 = time.time() print('结果:%s, 运行时间:%s'%(t, t2-t1))
输出结果以下:
结果:39088169, 运行时间:0.0
显然,使用动态规划法来求解斐波那契数列第n项的运行效率是很高的,由于,该算法的时间复杂度为多项式时间。
用递归法和动态规划法来求解该数列的第n项,完整的Python代码以下:
# calculate nth item of Fibonacci Sequence import time # recursive method def rec_fib(n): if n <= 1: return n else: return rec_fib(n-1) + rec_fib(n-2) # bottom up approach of Dynamic Programming def dp_fib(n): previousFib = 0 currentFib = 1 if n <= 1: return n # repeat n-1 times for _ in range(n-1): newFib = previousFib + currentFib previousFib = currentFib currentFib = newFib return currentFib # time cost of cursive method t1 = time.time() t = rec_fib(38) t2 = time.time() print('结果:%s, 运行时间:%s'%(t, t2-t1)) # time cose of DP method s = dp_fib(38) t3 = time.time() print('结果:%s, 运行时间:%s'%(t, t3-t2))
输出结果以下:
结果:39088169, 运行时间:22.42628264427185 结果:39088169, 运行时间:0.0