动态规划
先尝试着浅浅的理解一下动态规划,动态规划像是分治的某种变形,分治法是将问题分解成相同类型的子问题然后递归求解,动态规划也是差不多的说法,但是分治的子问题是相互独立的子问题,而动态规划的子问题具有相同的子子问题,避免掉这些问题的重复求解,就是动态规划。
动态规划一般用来解决最优化问题,下面的问题就是一个典型的最优化问题。
钢条切割问题(python年度更新系列)
给定一个长度为n的钢条,和钢条对应价格的价格表。
长度i: 1 2 3 4 5 6 7 8 9 10
价格Pi: 1 5 8 9 10 17 17 20 24 30
暴力求解的话就相当于一个0-1分布,每个单位1的位置都可以选择是否切割,长度为n的钢条有n-1个位置选择,也就是 2 n − 1 2^{n-1} 2n−1种不同的切割方案。
使用自顶向下的递归实现
每次切下来的那部分i不再切割,然后切割剩下的n-i,如此反复,这个情况相当于去递归的搜索这个问题
需要时间的递推公式为: T n = 1 + ∑ i = 0 n − 1 T i Tn =1 + \sum_{i=0}^{n-1}T_i Tn=1+∑i=0n−1Ti
故时间复杂度为: 2 n 2^{n} 2n
当n超过30时,这个数值将变得恐怖如斯。
class solution(object):
def search(p,n):
if n == 0:
return 0
Max = float('-inf')
for i in range(1,n):
Max = max(q,p[i]+search(p,n-i))
return Max
动态规划实现
动态规划的求解有两种思路,一是自顶向下的带备忘录的解法,另一种是自底向上的带备忘录的写法,二者的运行时间大致相同,均为 O ( n 2 ) O(n^2) O(n2)
自顶向下的动态规划:
class solution(object):
def MEMOIZED-CUT-ROD(p,n):
r = []
#创建一个备忘录
for i in range(n):
r.append(float('-inf'))
#创建一个数组用于记录每一次的最优情况
return MEMOIZED-UP-CUT-ROD(p,n,r)
def MEMOIZED-UP-CUT-ROD(p,n,r):
if r[n] > 0:
return r[n]
#如果最优情况已经发生,则返回这个最优值
#如果没有这个最优情况则去搜索
if n == 0:
#钢条的长度为0时cur_best = 0
cur_best = 0
else:
#否则暂时赋值为负无穷
cur_best = float('-inf')
#
for i in range(1,n):
cur_best = max(cur_best,p[i]+MEMOIZED-UP-CUT-ROD(p,n-i,r))
#cur_best从负无穷变为1的最优情况,2的最优情况,到3的最优情况
#也就是说max里面的cur_best代表的是不切割的收益,然后就是切割
#的各种情况的收益,当不存在备忘录时,就是2^n的时间复杂度,但
#存在备忘录时会避免很多子问题的重复求解,每个子问题只求解一次
r[n] = cur_best
return cur_best
自底向上的动态规划:
这个思路的话就不需要函数栈了,直接避免掉递归到函数底层才开始做备忘录,我直接开始解决子问题,然后不断的合并子问题
class solution(object):
def BOTTOM-UP-CUT-ROD(p,n):
r = []
for i in range(n):
r.append(float('-inf'))
#依然是初始化一个备忘录
r[0] = 0
#没有钢材的时候收益是0
for j in range(1,n):
cur_best = float('-inf')
for i in range(1,j):
#不切割和切割时的最优情况,由于从1开始找,因此每次都不用递归,直接找出最优就可以
cur_best = max(cur_best,p[i]+r[j-i])
r[j] = cur_best
#更新最优情况
return r[n]
时间复杂度为: n 2 n^2 n2
成功的降为了多项式级的时间复杂度。