zoukankan      html  css  js  c++  java
  • (基于Java)算法之动态规划——矩阵连乘问题

    动态规划(Dynamic Programming):与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适用于动态规划法求解的问题,经分解得到的子问题往往不是互相独立的

      使用动态规划法求解的问题需要符合一些条件:

    (1):所求解问题必须要符合最优子结构;(最优子结构即:原问题的最优解中包含了子问题的最优解)

    (2):原问题分解出来的子问题相互之间存在联系,即递归时会重复解决之前已解决过的子问题

     

      先说明一些前提:

    (1):矩阵相乘的条件是:前一个矩阵的行数=后一个矩阵的列数;

    (2):可以用数组p[0:n]来存放n个连乘矩阵的行数和列数(p[i-1]表示Ai的行数,p[i]表示Ai的列数);

    (3):用A[i:j]表示Ai连乘到Aj,假设最优的加括号方式(最外层)是:(Ai*……*Ak)(Ak+1*……*Aj) 。

     

    下面我们先从动态规划解决矩阵连乘问题的最初始的方法入手,代码如下:

     1 private static int recurMatrixChain(int i,int j,int[] p)   //最初始的矩阵连乘问题算法
     2 {
     3     if(i == j) return 0;  //i == j,即只有一个矩阵,计算次数当然为零
     4     int min = recurMatrixChain(i,i,p) + recurMatrixChain(i+1,j,p) + p[i-1] * p[i] * p[j];
     5     for(int k = i + 1; k < j; k++){
     6         int t = recurMatrixChain(i,k,p) + recurMatrixChain(k+1,j,p) + p[i-1] * p[k] * p[j];
     7         if(t < min) min = t; //从k处断开,如果t比min更小,则说明存在更优的解决方法,把t赋值给min
     8     }
     9     return min;
    10 }

      递归掌握得好的话,结合注释,理解起来并不会觉得困难。但这个方法存在一个严重的弊端,虽然能计算出最优解,但是在计算过程中,其实调用了指数级别次的方法,而这么多次调用其实都在解决重复子问题而已。

     

      下面介绍一种能解决上边提到的问题,动态规划解决矩阵连乘问题的第二种方法——备忘录方法,代码如下:

     1 private static int lookupChain(int i,int j,int[][] m,int[] p)
     2 {
     3     if(m[i][j] > 0) return m[i][j]; //如果m[i][j]非零,则说明该子问题被计算过,只需取出这个数,无需进行计算
     4     if(i == j) return 0;
     5     int min = lookupChain(i,i,m,p) + lookupChain(i+1,j,m,p) + p[i-1] * p[i] * p[j];
     6     for(int k = i + 1; k < j; k++){
     7         int t = lookupChain(i,k,m,p) + lookupChain(k+1,j,m,p) + p[i-1] * p[k] * p[j];
     8         if(t < min) min = t;
     9     }
    10     m[i][j] = min; //对于未记录的子问题,通过计算把该子问题的最优解求出后,存放在数组中
    11     return min;
    12 }
    13     
    14 private static int memoizedMatrixChain(int n,int[][] m,int[] p)   //这个就是解决矩阵连乘问题的备忘录方法
    15 {
    16     for(int i = 1; i <= n; i++)
    17         for(int j = 1; j <= n; j++)
    18             m[i][j] = 0;     //对数组进行初始化
    19     return lookupChain(1,n,m,p);
    20 }

      我们可以看出,lookupChain方法跟recurMatrixChain方法其实差不多,只是加插了两行代码而已,而这就是两种算法之间的不同之处,就是最重要的地方。第一次调用时跟第一种方法一样,同时记录了子问题的最优解,当第二次调用时,便可以直接从备忘录中提取子问题的最优解,大大地减少了方法的调用次数。

      下面介绍另一种动态规划方法——填表法,我的老师将其称为真·动态规划,代码如下:

     1 private static void matrixChain(int n,int[][] m,int[] p){    //填表法,我的老师也叫这方法为真·动态规划方法
     2     for(int i = n-1; i >= 1; i--)
     3         for(int j = i+1; j <= n; j++){
     4             int min = m[i][j] + m[i+1][j] + p[i-1] * p[i] * p[j];
     5             for(int k = i+1; k < j; k++){
     6                 int t = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
     7                 if(t < min) min = t;
     8             }
     9             m[i][j] = min;
    10         }
    11 }

      我们会发现,该算法其实跟第一种方法几乎相同,但是这个算法是直接用数组来存放子问题的最优解,跟备忘录方法稍有不同,而且这个算法并没有使用递归。而且这个填表法其实需要个人有比较强的能力,我们可以先举个简单点的例子,然后自己画一个二维数组,分析第一个数是填入到哪里,第二个数是填入到哪里,如此重复,找出规律后就可以开始编写自己的填表法了,在这里的是自下而上的填表法

      到这里结束了,如果有不对的地方或者对这个算法有更好的建议,欢迎指出!

  • 相关阅读:
    Git分支合并:Merge、Rebase的选择
    linux系统下MySQL表名区分大小写问题
    Spring Mvc和Spring Boot读取Profile方式
    Git删除远程分支
    TortoiseGit push免输密码
    git [command line] fatal: Authentication failed for
    Jenkins [Error] at org.codehaus.cargo.container.tomcat.internal.AbstractTomcatManagerDeployer.redeploy(AbstractTomcatManagerDeployer.java:192)
    FAIL
    WIN下修改host文件并立即生效
    MYSQL 创建数据库SQL
  • 原文地址:https://www.cnblogs.com/Not-Famous/p/3655430.html
Copyright © 2011-2022 走看看