算法与程序设计-动态规划01

参考链接:

动态规划——矩阵连乘问题算法及实现详解(附完整代码)-CSDN博客动态规划之——矩阵连乘(全网最详细博文,看这一篇就够了!)_矩阵连乘问题-CSDN博客

1.1矩阵连乘

1.1.1什么是矩阵连乘问题?

        找出一种使得做矩阵连乘时相乘次数最小的一个计算次序。

        矩阵连乘可以被递归成如上的形式。

        在这里举一个简单的例子我们要计算矩阵ABC的乘积,可以(AB)C,A(BC)两种计算次序,它们计算所得的结果相同,但相乘的次数不同。

        A(5x4) B(4x3) C(3x6)

        (AB)C的相乘次数为:150次

        A(BC)的相乘次数为:192次

        但是面对实际问题中,肯定不可能出现三个矩阵相乘,让我们求一个最优的计算次序。那么面对这个问题,应该怎么解决呢?

1.1.2矩阵连乘问题

问题描述:

矩阵A_1,A_2,A_3,...,A_n相乘,有一系列的整数P_0,P_1,...P_n,矩阵A_i的规模为P_{i-1}*P_{i},现计算出矩阵连乘所需要的计算次数最少是多少?

方案一:我们很容易可以想到,穷举所有的计算次序,然后得出它们的计算次数,找出最小的那个即可。

 是不是感觉我在发癫,与其穷举所有的计算次序再计算出所有的计算次数,还不如随便找一个计算次序算得了。(暴力算法的时间复杂度为\Omega (4^n/n^{3/2})

做事当然不能老是靠蛮力干,当然有巧妙的方法去求解它啦。(并不针对沸羊羊)

1.1.3动态规划解决矩阵连乘 

1.寻找最优子结构

在寻找计算次数最少的计算次序的过程,实际上就是给这一系列矩阵添加括号的过程。

现有矩阵A_i...A_n相乘,我们怎么样添加括号才能使得它的计算次数最少即获得最优解呢?

 我们从计算的最后一步考虑,不管是多少矩阵连乘,最后都会变成两个新的矩阵(B*C)相乘。

(A_i...A_{k-1})(A_k...A_n)=BC

那如果说,经过前面的一番猛虎操作,在获得B、C的过程中,各自的计算次数都是最少的,那么B*C再次运算得到的计算次数也就是最少的。(A_i...A_{k-1})就是一个最优子结构。(另一部分也是。)

那这个时候,我们就想了,如果B和C都能像上述所言,同样的被拆分成两个部分相乘,并且它们的各个部分计算次数都是最少的,那么环环相扣,是不是就能套出来最优解了。

答案是肯定的。

但又出现了新的问题,刚刚我们是假设已经知道了将连乘序列从那个部分拆分成子序列,但实际不知道,那么寻找括号的添加点即k的取值就是我们这个问题的重点。

2.最优子结构的递推式

定义m[i,j],(0<i<j<n)为计算A_i...A_j的最少计算次数,所以这个最小计算次数的问题可以用下列式子递归表出:m[i,j] = \begin{cases} \\0,&i=j \\min_{i<k<j}(m[i,k-1]+m[k,j]+p_{i-1}p_{k-1}p_j),&i<j \end{cases}

递推式解释:(当时老师讲这部分的时候,我的疑惑是为什么要加上p那一部分。)

A_i...A_j是整个连乘序列的子序列,我们可以将它看做成一个独立的连乘序列,再次寻找添加括号的位置,那么就会再次将这个连乘序列划分为两个子序列(即最优子结构),通过这种不断递归的方式,我们就可以得到矩阵连乘问题的最优解。

i=j时,不发生乘法运算,所以计算次数为0.

i<j时,A_i...A_j的最少计算次数为将该连乘序列自k处拆分的两个子序列的最少计算次数的和m[i,k-1]+m[k,j])加上生成的两个临时矩阵(两个子序列连乘之后得到的新的矩阵)相乘的计算次数(p_{i-1}p_{k-1}p_j)。

对于k值的选取,这个时候就需要我们用蛮力干活——穷举。我们只需要穷举j-i次,就可以得到k的取值。

那么,现在所有的问题就都解决啦。

3.使用“自底向上”的方式获得最优解

当我们使用m[i,j]=m[i,k-1]+m[k,j]+p_{i-1}p_{k-1}p_j时,我们首先要计算出m[i,k-1]m[k,j]的值。对于被分割的子序列长度都应该小于等于j-i,因此我们的算法以矩阵链长度增长的顺序计算。

关于这句话的解释:假设我们需要计算m[1,4],我们就需要要获得m[1,1]m[2,4]m[1,2]m[3,4]m[1,3]m[4,4]的值。那要获得m[1,3]的值,就又要获得m[1,1]m[2,3]m[1,2]m[2,3]的值。要获得m[1,2]的值,又需要获得m[1,1]m[2,2]的值。

我们来看这张图,我们需要得到m[1,4]的值,但是不能直接得到,而是从最底部开始(把这张图翻转一下) 先得到m[1,2],m[2,3],m[3,4]的值,通过递归得到m[1,4]的值。

这就是使用自底向上得到最优解。

4.使用“动态规划“算法的基本要素

存在递归关系,我的理解是,全局最优解需要拆分的部分得到局部的最优解。局部的最优解又可以继续拆分,还是需要更小部分的最优解。它们之间存在一个依赖关系。也就是说,从最开始我们就得把路选对,获得局部的最优解,之后才能获得全局的最优解。

(偏个题,大家不要把这种寻求全局最优解的方法映射到我们的人生中,我总是时常会悔恨之前所做的决定不够好,选择的路径不够正确,但是要记得,我们当时所做的决定已经是在以往的那个空间那个时刻最优的了,虽然可能现在看来很蠢,但是不要责备自己,继续向前走就好啦。)

通过上述,其实我们也能了解最优子结构的性质:当前最优解包含着其他子问题的最优解。

而最优子结构是使用动态规划算法求解的前提。

5.例子

假设我们现在有4个矩阵A_1,A_2,A_3,A_4组成的一个矩阵链,P_0=5,P_1=4,P_2=6,P_4=2,P_5=7。

现在,需要计算矩阵A_1,A_2,A_3,A_4乘积所需要的最少的乘法次数,即求出m[1,4]的值。

计算结果如上,过程不再赘述,套公式比大小得最值。

6.代码 

//没用递归
package com.algorithm.review;

import java.util.Scanner;

public class Matrix {
    public int [] matrixIndex=new int[100];
    public int [][] decision = new int[100][100];//分割界限记录
    public int [][]dp = new int[100][100];
    int matrixNum = 0;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Matrix matrix =new Matrix();
        matrix.matrixNum=scanner.nextInt();
        for (int i=0;i <= matrix.matrixNum;i++){
            matrix.matrixIndex[i] = scanner.nextInt();
        }
        matrix.matrixChain();
        System.out.println("括号划分:");
        matrix.print(1,matrix.matrixNum);
        System.out.println();
        System.out.println();
        System.out.println("最小连乘次数dp表:");
        for (int i=1;i<= matrix.matrixNum;i++){
            for (int k=1;k<=i;k++){
                System.out.print("null ");
            }
            for (int k=i+1;k<=matrix.matrixNum;k++)
            System.out.print(matrix.dp[i][k]+" ");
            System.out.println();
        }
        System.out.println();
        System.out.println("最小连乘次数:"+matrix.getMatrixResult());
    }


    public void  matrixChain(){
        for(int i=1;i<matrixNum;i++){//初始化对角线元素为零
            dp[i][i]=0;
        }
        int j=0;
        for(int length=2;length<=matrixNum;++length){//length表示矩阵连乘的矩阵数目
            for (int i=1;i<=matrixNum-length+1;++i){
                j=i+length-1;//length-1表示本次矩阵连乘的结果加和次数,j记录连乘矩阵的最后一个矩阵的下标
                dp[i][j] = dp[i+1][j]+matrixIndex[i-1]*matrixIndex[i]*matrixIndex[j];//先将分割界限定在i,即最后计算第一个矩阵与后面整体相乘
                decision[i][j] = i;//记录分割界限
                for (int k = i + 1; k < j; ++k){
                    int tmp = dp[i][k]+dp[k+1][j]+matrixIndex[i-1]*matrixIndex[k]*matrixIndex[j];//k作为分割界限,不断寻找更优的划分界限,使得该规模矩阵相乘计算次数尽可能小
                    if(dp[i][j]>tmp){//如果有更优解就更新
                        dp[i][j] = tmp;
                        decision[i][j] = k;//更新分割界限
                    }
                }
            }
        }
    }

    public void print(int i,int j){
        if(i==j){
            System.out.print("A["+i+"]");
            return ;
        }
        System.out.print("(");
        print(i,decision[i][j]);
        print(decision[i][j]+1,j);
        System.out.print(")");
    }

    public int getMatrixResult(){
        return dp[1][matrixNum];
    }
}

#include<iostream>
using namespace std;
#define N 7

//计算最优值 
void MatrixChain(int *p,int n,int m[][N],int s[][N]){
	for(int i=1;i<=n;i++){  //矩阵链中只有一个矩阵时,次数为0
		m[i][i]=0;
	}
	for(int r=2;r<=n;r++){
		for(int i=1;i<=n-r+1;i++){
			int j=i+r-1; //矩阵链的末尾矩阵,注意r-1,因为矩阵链为2时,实际是往右+1
			m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]; 
			s[i][j]=i;
			
			for(int k=i+1;k < j;k++){  //这里面将断链点从i+1开始,可以断链的点直到j-1为止
                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;
                }
            }
		}
		
	}
	
}

//构造最优解 
void Traceback(int i,int j,int s[][N]){
    if(i==j)       //回归条件
    {
        cout<<"A"<<i;
    }
    else       //按照最佳断点一分为二,接着继续递归
    {
        cout<<"(";
        Traceback(i,s[i][j],s);
        Traceback(s[i][j]+1,j,s);
        cout<<")";
    }
}
int main(){
	int p[N]={30,35,15,5,10,20,25};
	int m[N][N],s[N][N];
 
	MatrixChain(p,N-1,m,s); //N-1因为只有六个矩阵
	cout<<"矩阵的最佳乘积方式为: "; 
    Traceback(1,6,s);
	return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值