动态规划(Dynamic Programming)可以用来非常有效的解决许多搜索优化问题。这类问题都有相同的地方,原问题可以分解为子问题,子问题中有重复的部分(overlapping subproblem),或者原问题的最优解与子问题的最优解具有相同的结构(optimal substructure),将规模较小的子问题的最优解扩展为规模较大的问题的最优解。
它之所以有效,是因为它采用自底向上的方式递推求值,并把中间结果存储起来以便以后用来计算所需要求的解。
动态规划虽然比较容易理解,在应用过程中可以分为两步,先找出子问题的最优结构,然后找出子问题的最优解向规模较大子问题的最优解扩展。但在具体应用中会有各种各样的问题。下面将讨论各种动态规划问题,希望能加深对动态规划的理解。
动态规划典型问题之一,Feibonaci数列
f(1)=f(2)=1,f(n)=f(n-1)+f(n-2);
直接求解,用递归方式,可以方便的写出,
def fib(n): if n==1: return 1 elif n==2: return 1 else: return fib(n-1)+fib(n-2)
计算f(5),展开调用过程,如下,
f(5) = f(4) + f(3)
= f(3) + f(2) + f(2)+f(1)
= f(2)+f(1) + f(2) + f(2)+f(1)
可以看到,计算f(4)的时候会调用f(3),f(4)计算玩之后,又要调用f(3),里面有大量的重复计算。因为是递归调用,如果写成循环,从底向上计算,
def fib(n): if n==1: return 1 elif n==2: return 1 else: f = [1 for i in xrange(n)] for i in xrange(2,n): f[i]=f[i-1]+f[i-2] return f[n-1]
存储空间为n个,实际上,计算f(n)时只与前两项n-1和n-2相关,没有必要保存n项,所以代码可以简化如下,
def fib(n): n2, n1 = 0, 1 for i in range(n-2): n2, n1 = n1, n1 + n2 return n2+n1
这里定义了n=0时f(0)=0,参考http://20bits.com/article/introduction-to-dynamic-programming
Feibonaci数列问题,子问题有很多重复,换为自底向上计算,避免很多重复,计算效率提高。对于最优子结构的问题,可以以整数序列子序列最大和的问题为例,这里与刚才的一篇参考重合,不过还是这个例子比较好说明,就它了。
整数子序列最大和问题
给定一个整数序列,找出子序列的最大和。例如array=[1,2,-5,4,7,-2],我们可以直接看出,子序列最大和为4+7=11,那么计算机怎么求解呢?
求出所有子序列的和?子序列个数n(n+1)/2=sum(i,i=1,..n),复杂度n^2。
代码如下,
def msum(a): return max([(sum(a[j:i]), (j,i)) for i in range(1,len(a)+1) for j in range(i)])
有没有简单一点的方法呢?
动态规划。如果使用动态规划,要求n的解,从n-1的解扩充到n的解。如果知道n-1个数中最大子序列的和,增加一个数到n个数,最大子序列的和是什么呢?如果考虑这增加的一个数a[n],如果a[n]>=0,那么可以认为是增加和,那么最大子序列应该加上这个数。但是如果前n-1个数的最大子序列的和对应的子序列与a[n]不连续,那么怎么办呢?
换个角度考虑。如果考虑前n-1项最大子序列的和sum,如果sum<0,那么与后面的数相加,就会使最大子序列的和减小。
例如,a[j:k]是序列a[j:i]的最优子序列,s是最优子序列的和,t是a[j:i]的和,那么考虑下一个元素a[i+1],如果t+a[i+1]>=s,那么a[j:i+1]应该作为最优子序列,s=t+a[i+1],t=s;然而,如果t+a[i+1]<0,那么a[j:i+1]就不应该在最有子序列中,因为它会使子序列的和减少,所以此时,t=0,从下一个元素开始,对a[i+2:n],解新的问题。需要注意的是,s还是继续,保存最优子序列的和。
代码如下,
def msum2(a): bounds, s, t, j = (0,0), -float('infinity'), 0, 0 for i in range(len(a)): t = t + a[i] if t > s: bounds, s = (j, i+1), t if t < 0: t, j = 0, i+1 return (s, bounds)
参考链接里的代码又被我搬过来了,看着有写好的代码,自己就没有写的动力了。
使用动态规划算法的时间复杂度为O(n),只要对序列扫描一遍就可以了。
从这两个典型问题,可以看出动态规划问题的要义,把大问题化为小问题,在计算小问题的时候,避免重复计算,从小问题的解扩展到大问题的解的时候,要搞明白如何扩展,搞明白子问题的最有子结构是什么。
动态规划的思想虽然已经理解了,但遇到具体的问题的时候,又会有各种问题,所以我想尽可能多的列举动态规划问题,并给出解答。动态规划系列从这一篇开始,之后各种例题。