动态规划
引言
递归调用自身,但是问题的基础解通常是用递归函数的形式来说明的这种技术采取自底向上的方式递推求值,并把中间结果存储起来,以便将来用于计算所要求的解
从而用来解决许多组合最优化的问题
最长公共子序列问题
输入:两个字符串A,B,长度分别为n,m
输出:A和B最长公共子序列的长度
L[ i , j ]表示a1…a i 和b1…b j 的最长公共子序列的长度
- i = 0 or j = 0 :L[ i , j ] = 0( 所给的可能同时为空字符串)
- 递推式:
L [ i , j ] = { 0 if ( i = 0 or j = 0 ) L [ i − 1 , j − 1 ] + 1 if a i = b j max ( L [ i , j − 1 ] , L [ i − 1 , j ] ) if a i ≠ b j \begin{aligned} L[i,j] = & \begin{cases} 0 & \text{if } (i=0 \text{ or } j=0) \\ L[i-1,j-1] + 1 & \text{if } a_i = b_j \\ \max(L[i,j-1],L[i-1,j]) & \text{if } a_i \neq b_j \end{cases} \end{aligned} L[i,j]=⎩ ⎨ ⎧0L[i−1,j−1]+1max(L[i,j−1],L[i−1,j])if (i=0 or j=0)if ai=bjif ai=bj
ai = bj : 该位置=左上角值+1
ai != bj : 该位置=max{ 左边, 上边}
算法 LCS[^ time&space]
for i : 0 to n
L[i,0] = 0
end for
for j : 0 to m
L[0,j] = 0
end for
for i:1 to n
for j: 1 to m
if ai = bj then L[i,j]= L[i-1,j-1]+1 //等于左上角值+1
else L[i,j]= max{ L[i-1,j],L[i,j-1] } //等于左边和上边中最大的那个
end if
end for
end for
return L[n,m]
[^ time&space]:最优解能在Θ( nm )时间和Θ( min{ n,m} )的空间内得到
矩阵链相乘
输入:n个矩阵链的维数对应与正整数数组r[1…n+1] ,其中 r[1…n]是n个矩阵的行数 , r[n+1] 是Mn的列数
输出:n个矩阵链相乘的数量乘法的最少次数
假设有 n+1维数组 r1,r2,…r n+1,这里的ri和r i+1分别是矩阵M i中的行数和列数1
Mi,j表示MiMi+1…Mj的乘积
C[i,j]表示Mi,j相乘的数量乘法的次数
设k是i+1到j之间的任意索引,计算Mi,k-1和Mk,j,那么Mi,j=Mi,k-1 * Mk,j。2
算法MATCHAIN3
for i 1 to n
c[i,i]=0 //填充对角线do
end for
for d: 1 to n-1 //每一次迭代都进到下一条对角线
for i: 1 to n-d //每一次迭代都进到对角线的一个新项
j = i+d
comment:下列三行计算C[i,j]
C[i,j]=∞ //先将其初始化为一个非常大的值
for k: i+1 to j
C[i,j] = min{ C[i,j], C[i,k-1]+C[k,j]+ r[i]*r[k]*r[j+1] }
end for
end for
end for
return C[1,n]
动态规划范式
把子问题的解存储起来以避免重复计算是这种有效方法的基础
一个重要发现是,对于算法考虑的原问题的每一个子问题,算法都计算了一个最优解,称为最优化原理
所有点对的最短路径问题
输入: n * n维矩阵 l [ 1…n, 1…n ],这样有向图G中的边(i,j)的长度为 l [i , j]
输出:矩阵D,使得D[ i, j ]等于 i到 j的距离
di,jk :定义为从i到j,并且不经过{ k+1,k+2, …n }中任何顶点的最短路径长度
eg: di,j2 指的是从i到j,除了可能经过顶点1,顶点2或者同时经过他们而不经过任何其他顶点的最短路径
d i , j k = { l [ i , j ] if k = 0 min ( d i , j k − 1 , d i , k k − 1 + d k , j k − 1 ) if 1 ≤ k ≤ n d_{i,j}^k = \begin{cases} l[i,j] & \text{if } k=0 \\ \min(d_{i,j}^{k-1}, d_{i,k}^{k-1} + d_{k,j}^{k-1}) & \text{if } 1 \leq k \leq n \end{cases} di,jk={l[i,j]min(di,jk−1,di,kk−1+dk,jk−1)if k=0if 1≤k≤n
每个矩阵可以用公式Dk[i,j]=min{ Dk-1 [i,j],Dk-1 [i,k] + Dk-1 [k,j] }来计算(含k的表达式理解为第k个矩阵)
在第k次迭代中,第k行和第k列都是不变的,因此可以仅用D矩阵的一个副本来进行计算,例如以下算法
算法FLOYD4
D <- l //将输入矩阵l复制到D
for k: 1 to n
for i: 1 to n
for j: 1 to n
D[i,j] = min{ D[i,j],D[i,k] + D[k,j] }
end for
0/1背包问题5
输入:物品集合U= { u1,u2,…,un}, 容量为C的背包,体积分别为s1,s2,s3…sn,价值为v1,v2,…,vn。
输出:体积不大于C,最大总价值
V[i,j]表示从前i项取出的装入体积为j的背包的最大价值,要求的值是V[n,c]
易知V[0,j]和V[i,0]都=0
V [ i , j ] = { 0 if i = 0 or j = 0 V [ i − 1 , j ] if j < s i max { V [ i − 1 , j ] , V [ i − 1 , j − s i ] + v i } if i > 0 and j ≥ s i V[i,j] = \begin{cases} 0 & \text{if } i=0 \text{ or } j=0 \\ V[i-1,j] & \text{if } j < s_i \\ \max\{V[i-1,j], V[i-1,j-s_i] + v_i\} & \text{if } i > 0 \text{ and } j \geq s_i \end{cases} V[i,j]=⎩ ⎨ ⎧0V[i−1,j]max{V[i−1,j],V[i−1,j−si]+vi}if i=0 or j=0if j<siif i>0 and j≥si
利用上述公式逐行填充表格即可
算法KNAPSACK6
for i: 0 to n
V[i,0]= 0
for j: 0 to C
V[0,j]= 0
for i: 1 to n
for j: 1 to C
V[i,j] = V[i-1,j]
if si <= j then V[i,j]= max{ V[i,j], V[i-1,j-si]+ vi}
end for
end for
return V[n,C]