1. 什么是动态规划?
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。它把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法。
动态规划和分治法一样,动态规划(dynamicprogramming)是通过组合子问题而解决整个问题的解。但是分治法是将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解。而动态规划适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。
此时,分治法会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子问题只求解一次,将其结果保存起来,从而避免每次遇到各个子问题时重新计算答案。
2.动态规划算法的基本要素
(1)最优子结构
设计动态规划算法的第一步通常是刻画最优解结构。当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
(2)重叠子问题
子问题的重叠性质,也就是,在用递归算法自顶向下求解问题时,每次产生的问题并不是总是新问题,有些子问题被反复计算了多次。
3. 动态规划算法的设计
两种方法:
自顶向下(又称记忆化搜索、备忘录):基本上对应着递归函数实现,从大范围开始计算,要注意不断保存中间结果,避免重复计算
自底向上(递推):从小范围递推计算到大范围
动态规划的重点:
递归方程+边界条件
4. 矩阵连乘问题
(1)问题的描述
给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2,…,n-1。要算出这n个矩阵的连乘积A1A2…An。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。完全加括号的矩阵连乘积可递归地定义为:
1)(单个矩阵是完全加括号的(当然实际上可以不加);
2) 矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)。 例如,矩阵连乘积A1A2A3A4有5种不同的完全加括号的方式:(A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)。每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。若A是一个p×q矩阵,B是一个q×r矩阵,则计算其乘积C=AB的标准算法中,需要进行pqr次数乘。
3 )为了说明在计算矩阵连乘积时,加括号方式对整个计算量的影响,先考察3个矩阵{A1,A2,A3}连乘的情况。设这三个矩阵的维数分别为10×100,100×5,5×50。加括号的方式只有两种:((A1A2)A3),(A1(A2A3)),第一种方式需要的数乘次数为10×100×5+10×5×50=7500,第二种方式需要的数乘次数为100×5×50+10×100×50=75000。第二种加括号方式的计算量时第一种方式计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大的影响。于是,自然提出矩阵连乘积的最优计算次序问题,即对于给定的相继n个矩阵{A1,A2,…,An}(其中矩阵Ai的维数为pi-1×pi,i=1,2,…,n),如何确定计算矩阵连乘积A1A2…An的计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。 正如课堂中所分析,穷举法的计算量太大,它不是一个有效的算法,本实验采用动态规划算法解矩阵连乘积的最优计算次序问题。
(2)算法设计思想
动态规划算法的基本思想是将待求解问题分成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,动态规划法经分解得到的子问题往往不是相互独立的,前一子问题的解为后一子问题的解提供有用的信息,可以用一个表来记录所有已解决的子问题的答案,不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
本实验的算法思路是:
1)计算最优值算法MatrixChain():建立两张表(即程序中的**m和**s,利用二维指针存放),一张表存储矩阵相乘的最小运算量,主对角线上的值为0,依次求2个矩阵、3个矩阵…、直到n个矩阵相乘的最小运算量,其中每次矩阵相乘的最小运算量都在上一次矩阵相乘的最小运算量的基础上求得,最后一次求得的值即为n个矩阵相乘的最小运算量;另一张表存储最优断开位置。
2)输出矩阵结合方式s,自己加括号,构造出最优加括号方式。当然也可以编程输出,这部分不作强制要求。
程序设计:
<img src="https://img-blog.csdn.net/20150425103639451?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWFydmVsX2NoZW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
程序及详解:
#include <iostream>
#include <conio.h>
#include <time.h>
using std::cout;
using std::endl;
int main()
{
int p[]={30,35,15,5,10,20,25}; //p[0],p[1]确定A1行列数,p[1],p[2]确定A2行列数,依次类推
int n=sizeof(p)/sizeof(int)-1; //自动计算矩阵个数,增加程序灵活性
int i,j,k,r;
long **m=new long*[n+1]; int **s=new int*[n+1];
for(i=0;i<=n;i++) m[i]=new long[n+1]; //m 行列数n*n,下标都从1开始
for(i=0;i<=n;i++) s[i]=new int[n+1]; //s 行列数n*n,下标都从1开始
for(i=0;i<=n;i++) m[i][i]=s[i][i]=0; // 矩阵初始化
for (r = 2; r <= n; r++) /* 数组相乘个数 */
for (i = 1; i <= n - r+1; i++)
{
j=i+r-1;
m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j];
s[i][j] = i;
for (k = i+1; k < j; k++) /* 加括号方法得到的组数 */
{
long 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; /* 括号分割的位置 */
}
}
}
cout<<"图(b)显示结果如下:"<<endl;
for(i=0;i<=n;i++)
{
if (i==0)
{
cout<<"\t";
}
else
{
cout<<i<<"\t";
}
}
cout<<endl;
for (i=1;i<=n;i++)
{
cout<<i<<"\t";
for (int j=1;j<=n;j++)
{
if (m[i][j]>=0)
{
cout<<m[i][j]<<"\t";
}
else
{
cout<<"\t";
}
}
cout<<endl;
}
cout<<"图(c)显示结果如下:"<<endl;
for(i=0;i<=n;i++)
{
if (i==0)
{
cout<<"\t";
}
else
{
cout<<i<<"\t";
}
}
cout<<endl;
for (i=1;i<=n;i++)
{
cout<<i<<"\t";
for (int j=1;j<=n;j++)
{
if (s[i][j]>=0)
{
cout<<s[i][j]<<"\t";
}
else
{
cout<<"\t";
}
}
cout<<endl;
}
return 0;
}