分治方法:子问题互不相交,递归地求解子问题,再将他们组合起来。
动态规划:子问题之间相互重叠
最优子结构:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
两种等价的实现方法:
-
带备忘的自顶向下法
仍然按自然的递归形式编写,但过程会保存每个子问题的解。当需要一个子问题的解时,首先检查是否已经保存过此解,如果是,则直接返回保存的值,否则正常计算。 -
自底向上法
定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。因而我们将子问题按规模排序,由小至大的顺序进行求解。当求解某个子问题时,它所依赖的更小的子问题都已经求解完毕,结果已经保存。每个子问题只需要求解一次。
15.1 钢条切割
购买的长钢管切割为短钢管出售,求最佳切割方案。
价格表:
长度 i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 Pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
问题:给定一段长度为n英寸的钢条和一个价格表pi,求切割钢条方案,使得销售收益最大。
自顶向下过程的伪代码:
Memoized-cut-rod(p,n)
input: p: 价格的数组;n: 钢条的长度
- let r[0…n] be a new array
- for i=0 to n
- r[i] = -∞
- return Memoized-cut-rod-aux(p,n,r)
Memoized-cut-rod-aux(p,n,r)
- if r[n]>=0 # 检查所需值是否已知
-
return r[n] #若已知,直接返回保存值
- if n == 0
-
q = 0
- else q=-∞
-
for i = 1 to n
-
q = max(q, p[i]+Menoized-cut-rod-aux(p,n-i,r))
- r[n]=q
- return q
def memoized_cutrod(p,n):
r = [-1]*(n+1)
return memoized_cutrod_aux(p,n,r)
def memoized_cutrod_aux(p,n,r):
if r[n]>=0:
return r[n]
if n == 0:
q=0
else:
q=-1
for i in range(n):
if q<p[i]+memoized_cutrod_aux(p,n-i-1,r):
q = p[i]+memoized_cutrod_aux(p,n-i-1,r)
r[n]=q
return r[n]
自底向上版:
Bottom-up-cut-rod(p,n)
- let r[0…n] be a new array
- r[0] = 0
- for j= 1 to n
-
q = -∞
-
for i = 1 to j
-
q = max(q, p[i]+r[j-i])
-
r[j] = q
- return r[n]
import sys
import numpy as np
def bottom_up(p,n):
r = [None]*(n+1)
r[0] = 0
for j in range(1,n+1): # j is the length of rod
q = -1
for i in range(1,j+1): # i is the index of price
if q<p[i-1]+r[j-i]:
q = p[i-1]+r[j-i]
r[j]=q
return r[n]
if __name__ == "__main__":
p = [1,5,8,9,10,17,17,20,24,30]
n = 10
r = bottom_up(p, n)
print(r)
个人理解:
自底向上,就是从杆长为1开始计算每个长度的最优切割方案,并将最优值保存到r中。当计算新的杆长的切割方案时,进入一个小循环,从长度为1开始计算,分别为:p(1)+r(n-1),p(2)+r(n-2), …, p(n-1)+r(1),其中r(1)~r(n-1)都在之前计算过了,可以直接用。将其中最大的存到r(n)中。再计算n+1长度的杆的最优值。因此列表r中是所有长度的最优切割值。
渐近运行时间:Θ(n2)
上面两个算法只给出了最佳收益值,并没有给出最佳的切割方案。
求得最佳的切割方案是重构解。 自底向上法的拓展:
Extended-Bottom-up-cut-rod(p,n)
- let r[0…n] and s[0…n] be a new array
- r[0] = 0
- for j= 1 to n
-
q = -∞
-
for i = 1 to j
-
if q<p[i]+r[j-i]
-
q = p[i]+r[j-i]
-
s[j] = i
-
r[j] = q
- return r and s
Print-cut-rod-solution(p,n)
- (r,s) = Extended-Bottom-up-cut-rod(p,n)
- while n>0
-
print s[n]
-
n=n-s[n]
15.2 矩阵链乘法
**问题:**给定一个n个矩阵的序列<A1,A2,…, An>,我们希望计算他们的乘积。
通过括号规定具体的计算顺序,由于不同的计算顺序会导致不同的运算量,因此问题变成:
给定一个n个矩阵的序列<A1,A2,…, An>,矩阵Ai的规模为pi-1*pi(1<=i<=n),求完全括号化方案,使得计算乘积所需标量乘法次数最少。
动态规划法的步骤:
- 刻画一个最优解的值
- 递归地定义最优解的值
- 计算最优解的值,通常采用自底向上的方法
- 利用计算出的信息构造一个最优解。
步骤1 最优括号化方案的结构特征
递归寻找矩阵链中的最优分割点,因为,从最优分割点出发分开的两个子矩阵链,可以分别单独求解子最优分割点,再一次合并结果。
步骤2 一个递归求解方案
令m[i,j]表示计算矩阵Ai,j所需标量乘法次数的最小值。那么原问题A1,n所需的最低代价就是m[1,n].。
定义m[i,j],当i=j时,m[i,j]=0,即当中只包含一个矩阵不需要计算。
如果最优分割点在k, k+1之间,矩阵Ai的大小为pi-1*pi,则m[i,j]=m[i,k]+m[k+1,j]+pi-1pkpj
步骤3 计算最优代价
自底向上方法:此过程假定矩阵Ai的规模为pi-1*pi,输入是一个序列p=<p0,p1, …, pn>,其长度为n+1。此过程用一个辅助表m[1…n, 1…n]来保存代价m[i,j],用另一个辅助表s[1…n-1, 2…n]来记录分割点k。
伪代码:
Matrix-chain-order(p)
1 n=p.length-1
2 let m[1…n, 1…n] and s[1…n-1, 2…n] be new tables
3 for i =1 to n
4 m[i,i]=0
5 for l=2 to n // l is the chain length
6 for i =1 to n-l+1
7 j=i+l-1
8 m[i,j]=infinite
9 for k = i to j-1
10 q=m[i,k]+m[k+1,j]+pi-1pkpj
11 if q<m[i,j]
12 m[i,j]=q
13 s[i,j]=k
14 return m and s
import numpy as np
def matrix_chain(p):
n = len(p)-1
m = np.zeros([n, n])
s = np.zeros([n-1, n-1])
#
# for i in range(n):
# m[i][i]=0
for l in range(2,n+1):
for i in range(n-l+1):
j=i+l-1
m[i][j]= np.inf
for k in range(i, j):
q=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]
if q<m[i][j]:
m[i][j]=q
s[i][j-1]=k+1
return m,s
if __name__ == "__main__":
p = [30,35,15,5,10,20,25]
r,s = matrix_chain(p)
print(r , '\n', s)
输出:
[[ 0. 15750. 7875. 9375. 11875. 15125.]
[ 0. 0. 2625. 4375. 7125. 10500.]
[ 0. 0. 0. 750. 2500. 5375.]
[ 0. 0. 0. 0. 1000. 3500.]
[ 0. 0. 0. 0. 0. 5000.]
[ 0. 0. 0. 0. 0. 0.]]
[[1. 1. 3. 3. 3.]
[0. 2. 3. 3. 3.]
[0. 0. 3. 3. 3.]
[0. 0. 0. 4. 5.]
[0. 0. 0. 0. 5.]]
个人理解:
给出一个矩阵链的维度数据后,对于单个矩阵来说,即m[i,i],他的计算量为0。对于长度为2的矩阵来说,即m[i, i+1],他的计算量是p[i-1]*p[i]*p[i+1]。而当我们计算长度为3的矩阵链时,我们就可以用上长度为2的计算结果,再去乘以一个新的矩阵。因此循环的过程是,循环计算所有长度为2~n的矩阵链的最小值,并保存。
所以动态规划算法,就是要想清楚求解的过程中是如何使用之前的计算结果的。然后保存之前的计算结果,直接用,而不是每次都递归重新求,就是动态规划算法的思想吧。