什么是动态规划
看到把原始问题划分成一系列子问题,我们很容易想到分治算法,但是对于分治算法,很可能会遇到子问题被重复使用
就像下面这个矩阵连乘,分解为子问题就会出现三个重复子问题,用分治算法时,就会重复计算相同的子问题,使程序效率变得低下
所以,这就是动态规划的用武之地:对于一个问题,可分为多个相关子问题,子问题的解被重复使用
矩阵连乘问题
对于矩阵连乘,假设A为10 * 100 的矩阵, B为100 * 5 的矩阵,C为5 * 50的矩阵,那么有两种情况:
1、(A * B) * C 一共要乘 10 * 100 * 5 + 10 * 5 * 50 = 7500次
2、A * (B * C) 一共要乘 100 * 5 * 50 + 10 * 100 * 50 = 750000次
可以看到,不同顺序的相乘,就会有不同的时间复杂度,动态规划就是要来求一个最优解。
把这个括号的位置设为k,那么(A * B) * C 的k值就为2,表示A 和 B的矩阵先乘;A * (B * C) 的 k 值为 1, 表示B 和 C的矩阵相乘。
那么我们要做的就是如何确定这个k值,使得矩阵相乘具有最优解。
m[i, j]是指矩阵 i 到 矩阵 j 相乘的最小乘法数
p之间相乘是指拆分的矩阵之间的相乘
对于A * B * C:
m[1,3] = m[1,1] + m[2, 3] + p0 * p1 * p3 的意思是 A * (B * C)
m[1,3] = m[1,2] + m[3, 3] + p0 * p2 * p3 的意思是 (A * B) * C
也可以用这个图来理解
例如求m[1, 3] 就是 求 m[1, 1] 和 m[2, 3] 或者 求 m[1, 2] 和 m[3, 3。
代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define SIZE 100
#define INF 999999999
int p[SIZE];
int m[SIZE][SIZE]; //存放矩阵链计算的最优值,d[i][j]为第i个矩阵到第j个矩阵的矩阵链的最优值
int Best_DP(int n)
{
memset(m, 0, sizeof(m));
int len;
for (len = 1; len <= n; len++)
{
int i, j, k;
for (i = 1, j = i+len; j <= n; i++, j++)
{
int min = INF;
for (k = i; k < j; k++)
{
int count = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
if (count < min)
{
min = count;
}
}
m[i][j] = min;
}
}
return m[1][n];
}
int main(void)
{
int n;
while (scanf("%d", &n) != EOF)
{
int i;
for (i = 0; i <= n; i++)
{
scanf("%d", &p[i]);
}
printf("%d\n", Best_DP(n));
}
return 0;
}
这里需要注意的是:
- n是指相乘的矩阵数目
- p用来存放各矩阵的信息: 用p[i - 1] 和 p[i]来表示第i个矩阵的行和列,例如 A为10 * 100 的矩阵, B为100 * 5 的矩阵,C为5 * 50的矩阵,则p[0] = 10 , p[1] = 100, p[2] = 5 , p[3] = 50
- m[i, j]用来表示i 到 j矩阵相乘的最优解