目录
1、基本思想
动态规划法与分治法类似,也是将要求解的问题一层一层地分解成一级一级、规模逐步缩小的子问题,直到可以直接求解其解的子问题为止。
所有子问题按层次关系构成一棵子问题树。树根是原问题。原问题的解依赖于子问题树中所有子问题的解。
与分治法不同的是,子问题往往不是相互独立的。
动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。因此动态规划法的相应特征是,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。
动态规划法通常用于求一个问题在某种意义下的最优解。适合采用动态规划方法的优化问题必须具备最优子结构性质和子问题重叠性质。
当一个问题的优化解包含了子问题的优化解时,则称该问题具有优化子结构性质。在求解一个问题的过程中,很多子问题的解被多次调用,则称该问题具有子问题的重叠性质。
2、动态规划算法设计步骤
- 分析最优解的性质,并刻画其结构特征。
- 递归地定义最优值。
- 根据递归方程分解子问题,直到不能分。
- 自底向上的方式计算最优值,并记录构造最优解所需的信息。
- 根据计算最优值时得到的信息,构造一个最优解。
3、矩阵连乘最佳计算次序问题
矩阵连乘积问题是给定n个矩阵{A1,A2,.....,An},其中Ai和Ai+1是可乘的,i=1,2....n-1。要求这n个矩阵连乘积A和Ai+1是可乘的,i=1,2.....n-1.要求这n个矩阵连乘积A1A2....An具有最小代价的计算次序。
例1:
矩阵连乘积A1A2A3A4可以有以下5种不同的完全加括号方式,5种方式中有一种是最小代价的计算次序。
例2:
我们来看一个计算3个矩阵{A1,A2,A3 }的连乘积的例子。
设这3个矩阵的维数分别为100*10,5*100,和50*5。
若按第一种加括号方式((A1A2)A3)来计算,总共需要10*100*5+ 10*5*50=7500次的数乘。
若按第二种加括号(A1(A2A3))来计算,则需要的数乘次数为100*5*50+ 10*100*50=75000。
第二种加括号的方式是第一种加括号方式的计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大影响。
解矩阵连乘积的最优计算次序问题最容易想到的方法是穷举搜索法。也就是列出所有可能的计算次序,并计算出每一种计算次序相应需要的计算量,然后找出较小者。然而,这样做计算量太大。因此,穷举搜索法不是有效算法。下面用动态规划方法解矩阵连乘积的最优计算次序问题。
分析最优解的结构
为方便,将矩阵连乘积Ai,(Ai+1)...,Aj简记为A[i:j]。
来看计算A[1:n]的一个最优次序。设这个计算次序在矩阵Ak和Ak+1之间将矩阵链断开,1<=k<n,则完全加括号方式为((A1...Ak)(Ak+1...An))。照此,要先计算A[1:k]和A[k+1:n],然后,将所得的结果相乘才得到A[1:n]。
显然其总计算量为计算A[1:k]的计算量加上计算A[k+1:n]的计算量再加上A[1:k]和A[k+1:n]相乘的计算量。
这个问题的一个关键特征是:计算A[1:n]的一个最优次序所包含的计算A[1:k]和A[k+1:n]的次序也是最优的。
因此,矩阵连乘积计算次序问题的最优解包含着其子问题最优解,这种性质称为最优子结构性质。
例3:
矩阵计算:
如:A1 = 30×35 A2 = 35×15 A3 = 15×5 三个矩阵
A1 × A2 = 30×35×15
(A1 × A2 )× A3 = 30×35×15+30×15×5
A1 × (A2 × A3) = 35×15×5+30×35×5
例:确定计算矩阵连乘积A1A2A3A4A5A6的最优次序,其中各个矩阵的维数为:
构造如下表:
① m[1][1]...m[6][6]为0,
② m[1][2] = A1×A2 = 15750,m[2][3] = A2×A3 = 2625.......
③
m[1][3] = min{
A1(A2A3)
(A1A2)A3
}
递归式有:
m[1][3] = min{
m[1][1]+m[2][3]+A1A3 = 0 + 2625 + 30×35×5 = 7875
m[1][2]+m[3][3]+A2A3 = 30×35×15 + 0 + 35×15×5 = 18375
}
所以在表[1][3]处填入7875,同时得出最佳断开点为1即A1(A2A3)。
再举一例:m[2][5] = min{
m[2][2]+m[3][5]+A2A5 = 0+2500+35×15×20 = 13000
m[2][3]+m[4][5]+A3A5 = 2625+1000+35×5×20 = 7125
m[2][4]+m[5][5]+A4A5 = 4375+0+35×10×20 = 11375
}
所以在表[2][5]处填入7125,同时得出最佳断开点为3即(A2A3)(A4A5)。
.....
④上表中可以看出15125是A1A2A3A4A5A6的最佳计算序列值;
11875是A1A2A3A4A5最佳计算序列值.....
void MatrixChain(int *p,int n,int **m,int **s){
for(int i=1;i<=n;i++)
m[i][i] = 0;//只有一个矩阵时的最小代价
for(int r=2;r<=n;r++)//有r个矩阵的最小代价
for(int i=1;i<=n-r+1;i++){//n-r+1为对角线长度
int j = i+r+1;//最优值列标
m[i][j] = m[i+1][j]+p[i-1]*p[i]*p[j];//省略m[i][i] = 0
s[i][j] = i;
for(int k=i+1;k<j;k++){
int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t < m[i][j]){
m[i][j] = t;
s[i][j] = k;//记录最佳断点位置
}
}
}
}
时间复杂度为:O(n)
4、最大子段和问题
前一文章使用分治算法已经做过一遍,这里不再描述问题
例:
用动态规划法求a = {3,-2,-1,9,-3,5}的最大子段和
构造下表:
其中,j为数组下标索引,b[j]为暂存最大子段和,i为开始,j为结束,besti记录最大子段和的开始位置,bestj记录最大子段和的结束位置,sum记录当前最大子段和。
j b[j] i j besti bestj sum 0 3 0 0 0 0 3 1 1 0 1 0 0 3 2 0 0 2 0 0 3 3 9 3 3 3 3 9 4 6 3 4 3 3 9 5 11 3 5 3 5 11 当b[j] < 0 时 ,再计算则无意义,使i 从j开始即i = j。
当b[j]>sum时,使sum = b[j]
int besti = 0,bestj = 0;
int maxsum(int n,int *a){
int sum = 0,b = 0;
for(int j = 0;j<n;j++){
if(b>0)b+=a[j];
else {
b = a[j];
i = j;
}
if(b > sum){
sum = b;
besti = i;
bestj = j;
}
}
return sum;
}
时间复杂度:O(n)
最长公共子序列问题
什么是子序列?
例:
X = <A,B,C,B,D,B>
Z = <B,C,D,B>是X的子序列
W = <B,D,A>不是X的子序列
什么是最长公共子序列?
例:
X = <A,B,C,B,D,A,B>
Y = <B,D,C,A,B,A>
Z = <B,C,A>是X与Y的一个公共子序列
L = <B,C,B,A>是X与Y的一个最长公共子序列
最长公共子序列(LCS)结构分析
第i前缀:设X = <x1,x2,......,xm>是一个学列,X的第i前缀Xi是一个序列,定义为Xi = <x1,x2,......xi>。约定X0是个空序列。
定理(LCS的最优子结构性质):
设X = <x1,x2,......,xm>和Y = <y1,y2,......,yn>是两个序列,Z = <z1,z2,......zk>是X与Y的LCS,则有:
(1)若xm = yn,则zk = xm = yn,且Zk-1是Xm-1和Yn-1的LCS。
(2)若xm != yn,且zk != xm,则Z是Xm-1和Y的LCS。
(3)若xm != yn,且zk !=yn,则Z是X和Yn-1的LCS。
这个定理告诉我们,两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质。
由递归结构可以看出LCS问题具有子问题的重叠性,因此可用动态规划算法解决。