动态规划(之前的背包问题也是动态规划的一种)
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是:适用于动态规划法求解的问题,经分解得到的子问题往往不是互相独立的。
目的:保存已解决的子文题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式时间的算法。
基本思路:用一个表来记录所有已解决的子问题的答案。
常用范围:通常用于求解具有某种最优性质的问题。
步骤如下:
(1)找出最优解的性质并刻画其结构特征
(2)递归的定义最优值
(3)以自底向上的方式计算出最优值
(4)根据计算最优值时得到的信息,构造一个最优解。
矩阵连乘问题
问题概述:
由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。而计算次序与计算量有着密切的关系,所以,我们的目的就是找出最优的计算次序,使得我们的计算量最少。
基础知识
设A是一个p*q矩阵,B是一个q*r矩阵,那么C=AB是一个p*r矩阵,从我们需要p*q*r次数乘
(1)分析最优解的结构
计算A[1:n],可以拆分成A[1:k]和A[k+1:n],然后将计算结果相乘,结果不变。
所以A[1:n]的计算了等于A[1:k]和A[k+1:n]的计算量之和再加上他们结果相乘的计算量的和。
关键特征:A[1:n]的一个最优次序所包含的计算矩阵子链A[1:k]和A[k+1,n]的次序也是最优的。
最优子结构性质
一个问题的最优解包含着其子问题的最优解。
一个问题的最优子结构性质是该问题可用动态规划算法求解的显著特征。
建立递归关系
对于矩阵连乘积的最优计算次序问题,设计算A[i:j],所需的最少数乘次数时m[i][j]则元问题的最优值为m[1][n]。
当i=j时,A[i:j]=Ai为单一矩阵,无需计算,因此m[i][i]=0,i=1,2,3…n
m[i][j]=m[i][k]+m[k+1][j]+pr-1*pk*pj
k有j-1个位置,要在这j-1个位置中找出最适合的
计算最优值
在动态规划中解决递归问题,可以采用自底向上的方式进行计算,保留已解决的子问题答案。
由MatrixChain可以计算出最优值
(1)i,j之间差1,先计算出[1,2] [2,3] [3,4]这些两两相邻的矩阵
(2)i,j之间差2,然后计算[1,3] [2,4] [3,5]这些两个两个的矩阵,这样就可以用上之前一个一个的结果了,并且k从第一个开始断,检测,拿[1,3]举例子,[1,3]可以组成三个矩阵A,B,C那么就有(AB)C或A(BC)这两种不同的差别
(3)最终在(0,6)的位置上的就是所得的最小的计算量的值了
构造最优解
前面只是得出了最优解的值,但是没有并没有给出最优解
我们可以在计算最优解的同时,用一个矩阵s来记录k(也就是从第几个矩阵断开)的取值,这样就可以用Backtrack得出结论
代码
#include<iostream>
#include<iomanip>
#include<stack>
#include<queue>
using namespace std;
#include<malloc.h>
#include<string.h>
#include<stdlib.h>
int ** Get2Array(int n, int m)
{
int **s = (int**)malloc(sizeof(int*)*n);
for (int i = 0; i<n; ++i)
{
s[i] = (int*)malloc(sizeof(int)*m);
memset(s[i], 0, sizeof(int)*m);
}
return s;
}
void Free2Array(int **p, int n)
{
for (int i = 0; i<n; ++i)
{
free(p[i]);
}
free(p);
}
void Print_2Array(int **p, int n, int m)
{
for (int i = 0; i<n; ++i)
{
for (int j = 0; j<m; ++j)
{
cout << setw(7) << p[i][j];
}
cout << endl;
}
cout << endl;
}
//////////////////////////////////////
void MatrixMul(int **a, int **b, int **c,//a*b=c
int ra, int ca, int rb, int cb)
{
if (ca != rb) return;
for (int i = 0; i<ra; ++i)
{
for (int j = 0; j<cb; ++j)
{
int tmp = 0;
for (int k = 0; k<ca; ++k)
{
tmp += a[i][k] * b[k][j];
}
c[i][j] = tmp;
}
}
}//矩阵相乘
void Backtrack(int **s, int i, int j)//s 1 6
{//关于1 到6 的最优解
if (i < j)
{
Backtrack(s, i, s[i][j]);//递归1到s[1][6]//这里存储的是最后一个段点,先递归前面的
Backtrack(s, s[i][j] + 1, j);
cout << "Matrix A " << i << " , " << s[i][j] << " and A ";
cout << s[i][j] + 1 << " , " << j << endl;
}
}
int MatrixChain2(int *p, int n, int **m, int **s)//6个矩阵
{// p 6 m s
int i, j;
for (i = 1; i <= n; ++i) m[i][i] = 0;//对角线上的元素都设置成0,1~6
for (int len = 2; len <= n; ++len)//2~6 3
{//从二开始断12 23 34 45 56
for (i = 1; i <= n - len + 1; ++i)//1~5
{//1 2
j = i + len - 1;//1+2-1 = 2 3 2
//1 2 2/3 2/3 0/1 1/2 2/3
//1 3 2 3 0 1 3
m[i][j] = 0 + m[i + 1][j] + p[i - 1] * p[i] * p[j];//计算出m[1][2]就是1~2的值
s[i][j] = i;//s[1][2] = 1
for (int k = i + 1; k < j; ++k)//遍历i,j之间j-1个位置,然后进行比较
{//2 3
// 1 2 3 3 0 2 3
int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
if (m[i][j] > t)
{
m[i][j] = t;
s[i][j] = k;
}
}
}
Print_2Array(m, n + 1, n + 1);
}
return m[1][n];
}
int main()
{
const int n = 6;
int p[n + 1] = { 30,35,15,5,10,20,25 };//30*35 35*15 15*5 5*10 10*20 20*25
int **m = Get2Array(n + 1, n + 1);//7*7 0~6
int **s = Get2Array(n + 1, n + 1);
int min = MatrixChain2(p, n, m, s);
cout << "Min: " << min << endl;
Print_2Array(m, n + 1, n + 1);
Print_2Array(s, n + 1, n + 1);
Backtrack(s, 1, n);
return 0;
}