动态规划-矩阵链乘法

问题描述:

  给定由n个要相乘的矩阵构成的序列(链)<A1,A2,...,An>,要计算乘积A1A2...An,可以将两个矩阵相乘的标准算法作为一个子程序,通过加括号确定计算的顺序(对同一矩阵链,不同的计算顺序所需要的计算次数大不相同)。

  目标问题:给定n个矩阵构成的矩阵链<A1,A2,...,An>,其中,i=1,2,...,n,矩阵Ai的维数为pi-1×pi,对乘积A1A2...An以一种最小计算次数加全部括号。

穷尽搜索:

  令P(n)表示一串n个矩阵可能的加全部方案数。当n=1时,只有一种方案。当n>1时,一个矩阵链乘积变成两个子矩阵链乘积的乘积,分裂的地方可能发生在第k个矩阵和第k+1个矩阵之间,k=2,3,...,n-1。那么,P(n)=∑(k=2,3,...,n-1)P(k)*P(n-k)。这是关于n的指数形式,因此穷尽搜索不是好的策略。

动态规划:

  1、寻找最优子结构

  寻找最优子结构,利用这一子结构,根据子问题的最优解构造原问题的一个最优解。对于矩阵链相乘的问题,用Ai..j表示A1A2...An的求积结果,其中i≤j。对于某个k(1<k<j),首先计算Ai..k和Ak+1..j,然后计算它们相乘最终得到Ai..j。计算Ai..j的代价就是计算Ai..k和Ak+1..j的代价之和,再加上两者相乘的代价。

  最优子结构如下:

  假设AiAi+1...Aj的最优加括号方式在k和k+1处分开,则对前缀子链AiAi+1...Ak的加括号必须是一个最优加括号,为什么呢?如果存在有对AiAi+1...Ak有一个更优的加括号,那么会产生一个对AiAi+1...Aj的更优加括号,这与假设矛盾。同理,后缀子链Ak+1Ak+2...Aj也必须是一个最优加括号。

  现在利用最优子结构,根据子问题的最优解构造原问题的最优解。即保证寻找一个最优的位置分割乘积。

  2、递归求解

  假设m[i,j]表示计算AiAi+1...Aj最优解的花费代价,1≤i≤j≤n。整个问题,计算A1..n的代价为m[1,n]。如果i=j,m[i,j]=0;当i<j时,假设最优加括号是将AiAi+1...Aj分裂成AiAi+1...Ak和Ak+1Ak+2...Aj。则m[i,j]等于计算Ai..k和Ak+1..j的最小代价,再加上这二者相乘的代价。即:

    m[i,j]=m[i,k]+m[k+1,j]+pi-1pkpj (i<k<j)

  上述公式假设已经知道k值,实际上,只知道k有j-i个值,分别是k=i,i+1,i+2...,j-1,k是其中最佳的值,需要通过如下方式确定k值:

    m[i,j]=min(i≤k<j){m[i,j]=m[i,k]+m[k+1,j]+pi-1pkpj } (i<j)

  各个m[i,j]的值给出了子问题的最优解的代价。为了跟踪如何构造一个最优解,定义s[i,j],让其记录AiAi+1...Aj最优解分裂处的k值。

  3、计算最优解的代价

    可轻易根据上述递归公式写出计算m[1,n]的递归算法。但是,它和穷尽搜索类似,具有指数时间。

    可以观察到,原问题只有较少的子问题:每一对满足i<j的(i,j)对应一个子问题,总共不超过n2个。一个递归算法在递归树的不同分支中可能会多次遇到同一个子问题,子问题重叠是判断是否能够采用动态规划方法的第二个标志(第一个标志是最优子结构)。

    对于矩阵链相乘问题不使用递归方式,而是使用自底向上的表格法计算最优解的代价。如下代码计算最优解的代价,其中Ai的维数为pi-1×pi,i=1,2,....,n。输入序列p=<p0,p1,p2,...,pn>,使用一个辅助表m[1...n,1...n]保存m[i,j]的值,使用s[1...n,1...n]记录取得最优m[i,j]时分裂处的k值。s用来构造最优解。    

 1 public void Matrix_Chain_Order()
 2     {
 3         for (int i = 1; i <= n; i++) {
 4             m[i][i] = 0;   
 5         }
 6         for (int l = 2; l <= n; l++)
 7         {
 8             for (int i = 1; i <= n-l+1; i++)
 9             {
10                 int j = i+l-1;
11                 m[i][j] = 2147483647;
12                 for (int k = i; k <= j-1; k++) 
13                 {
14                     int q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
15                     if(q<m[i][j])
16                     {
17                         m[i][j] = q;
18                         s[i][j] = k;
19                     }                                       
20                 }               
21             }         
22         }    
23      }  

  4、构造最优解

     利用保存在表格s[1..n,1..n]内的、经过计算的信息来构造一个最优解并不难。在每一个表项s[i,j]中记录了AiAi+1..Aj在Ak与Ak+1之间取得最优解的k值。计算A1..n最优解时,最终相乘的顺序就是A1..s[1,n]As[1,n]+1..n。s[1,s[1.n]]决定A1..s[1,n]最优解处的值,s[s[1,n]+1,n]则决定As[1,n]+1..n最优解处的值。下面程序递归的计算出一个矩阵链相乘的最优顺序。  

 1 public void Print_Optimal_Parens(int i,int j)
 2     {
 3         if(i==j)
 4         {
 5             System.out.print("A"+i);
 6         }
 7         else
 8         {        
 9             System.out.print("(");
10             Print_Optimal_Parens(i, s[i][j]);
11             Print_Optimal_Parens(s[i][j]+1, j);
12             System.out.print(")");
13         }
14     }

 程序实现:   

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agrith;

/**
 *
 * @author ning
 */
public class Matrix_Chain {
    private int [] p;
    private int [][]s;
    private int [][] m;  
    private int n;

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }
    
    
    public int[] getP() {
        return p;
    }

    public void setP(int[] p) {
        this.p = p;
    }

    public int[][] getS() {
        return s;
    }

    public void setS(int[][] s) {
        this.s = s;
    }

    public int getM(int i,int j) {
        return m[i][j];
    }

    public void setM(int[][] m) {
        this.m = m;
    }

    public void Matrix_Chain_Order()
    {
        for (int i = 1; i <= n; i++) {
            m[i][i] = 0;   
        }
        for (int l = 2; l <= n; l++)
        {
            for (int i = 1; i <= n-l+1; i++)
            {
                int j = i+l-1;
                m[i][j] = 2147483647;
                for (int k = i; k <= j-1; k++) 
                {
                    int q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                    if(q<m[i][j])
                    {
                        m[i][j] = q;
                        s[i][j] = k;
                    }                                       
                }               
            }         
        }    
     }  
    
    public void Print_Optimal_Parens(int i,int j)
    {
        if(i==j)
        {
            System.out.print("A"+i);
        }
        else
        {        
            System.out.print("(");
            Print_Optimal_Parens(i, s[i][j]);
            Print_Optimal_Parens(s[i][j]+1, j);
            System.out.print(")");
        }
    }
}  
        matrix_Chain.Matrix_Chain_Order();
        System.out.println("最优解花费的代价:"+matrix_Chain.getM(1, n-1));
        
        System.out.println("最优解的顺序:");
        matrix_Chain.Print_Optimal_Parens(1, n-1);
        
    }
    
}

程序结果:

  最优解花费的代价:15125
  最优解的顺序:
  ((A1(A2A3))((A4A5)A6))

 

posted @ 2014-07-29 18:26  问路书童  阅读(428)  评论(0编辑  收藏  举报