首先我们来看看动态规划的四个步骤:
1. 找出最优解的性质,并且刻画其结构特性;
2. 递归的定义最优解;
3. 以自底向上的方式刻画最优值;
4. 根据计算最优值时候得到的信息,构造最优解
其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下:
一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好。此时,动态规划算法没有任何多余的计算。同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算法的计算时间和空间需求。当子问题空间中的部分子问题可以不必求解时,使用备忘录方法则较为有利,因为从其控制结构可以看出,该方法只解那些确实需要求解的问题。
对于动态规划算法,我们必须明确两个基本要素,这两个要素对于在设计求解具体问题的算法时,是否选择动态规划算法具有指导意义:
1 算法有效性依赖于问题本身所具有的最优子结构性质:设计算法的第一步通常是要刻画最优解的结构。当问题的最优解包含了子问题的最优解时,称该问题具有最优子结构性质。问题的最优子结构性质提供了该问题可以使用动态规划算法求解的重要线索
在矩阵连乘积最优次序问题中注意到,若A1A2…An的最优完全加括号方式在Ak和Ak+1之间断开,则由此可以确定的子链A1A2A3…Ak和Ak+1Ak+2…An的完全加括号方式也最优,即该问题具有最优子结构性质。在分析该问题的最优子结构性质时候,所使用的方法具有普遍性。首先假设由原问题导出的子问题的借不是最优解,然后在设法说明在这个假设下可以构造出比原问题最优解更好的解,从而导致矛盾。
在动态规划算法中,利用问题的最优子结构性质,以自底向上的方式递归的从子问题的最优解逐渐构造出整个问题的最优解。算法考察的子问题的空间规模较小。例如在举证连乘积的最优计算次序问题中,子问题空间由矩阵链的所有不用的子链组成。所有不用的子链的个数为o(n*n),因而子问题的空间规模为o(n*n)
2 可以用动态规划算法求解问题应该具备另一个基本要素是子问题的重叠性。在用递归算法自顶向下求解此问题时候,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题都只是求解一次,而后将其保存到一个表格中,当再次需要解此问题时,只是简单使用常数时间查看一下结果。通常,不同子问题个数随着问题大小呈多项式增长。因此使用动态规划算法通常只是需要多项式时间,从而获得较高的解题效率。
下面是使用动态规划算法求解矩阵连乘问题的Java实现:
package dynamic_planning;
public class Strassen {
/*
* array[i][j]表示Ai...Aj相乘最少计算次数
* s[i][j]=k,表示Ai...Aj这(j-i+1)个矩阵中最优子结构为Ai...Ak和A(k+1)...Aj
* p[i]表示Ai的行数,p[i+1]表示Ai的列数
*/
private int array[][];
private int p[];
private int s[][];
public Strassen(){
p=new int[]{2,4,5,5,3};
array=new int[4][4];
s=new int[4][4];
}
public Strassen(int n,int []p){
this.p=new int[n+1];
this.array=new int[n][n];
this.s=new int[4][4];
for(int i=0;i<p.length;i++)
this.p[i]=p[i];
}
/*********************方法一,动态规划**********************************/
public void martixChain(){
int n=array.length;
for(int i=0;i<n;i++)
array[i][i]=0;
for(int r=2;r<=n;r++){
for(int i=0;i<=n-r;i++){
int j=i+r-1;
array[i][j]=array[i+1][j]+p[i]*p[i+1]*p[j+1];
s[i][j]=i;
for(int k=i+1;k<j;k++){
int t=array[i][k]+array[k+1][j]+p[i]*p[k+1]*p[j];
if(t<array[i][j]){
array[i][j]=t;
s[i][j]=k;
}
}
}
}
}
/*
* 如果待求矩阵为:Ap...Aq,then a=0,b=q-p
*/
public void traceBack(int a,int b){
if(a<b){
traceBack(a, s[a][b]);
traceBack(s[a][b]+1, b);
System.out.println("先把A"+a+"到A"+s[a][b]+"括起来,在把A"+(s[a][b]+1)+"到A"+b+"括起来,然后把A"+a+"到A"+b+"括起来");
}
}
/*********************方法二:备忘录方法*****************************/
public int memorizedMatrixChain(){
int n=array.length;
for(int i=0;i<n;i++){
for(int j=i;j<n;j++)
array[i][j]=0;
}
return lookUpChain(0,n-1);
}
public int lookUpChain(int a,int b){
if(array[a][b]!=0)
return array[a][b];
if(a==b)
return 0;
array[a][b]=lookUpChain(a, a)+lookUpChain(a+1, b)+p[a]*p[a+1]*p[b+1];
s[a][b]=a;
for(int k=a+1;k<b;k++){
int t=lookUpChain(a, k)+lookUpChain(k+1, b)+p[a]*p[k+1]*p[b+1];
if(t<array[a][b]){
array[a][b]=t;
s[a][b]=k;
}
}
return array[a][b];
}
public static void main(String[] args) {
Strassen strassen=new Strassen();
//strassen.martixChain();
strassen.memorizedMatrixChain();
strassen.traceBack(0, 3);
}
}