动态规划 矩阵连乘问题

两个矩阵相乘的计算量

枚举所有完全加括号方式

ABCD四个矩阵连乘

1、(A(BCD))——>(A(B(CD))),(A((BC)D));
2、((AB)(CD))——>NULL;
3、((ABC)D)——>((A(BC)D)),(((AB)C)D);
对于上面四个矩阵来说,枚举方法是:
1、括号加在A和B之间,矩阵链被分为(A)和(BCD);
2、括号加在B和C之间,矩阵链被分为(AB)和(CD);
3、括号加在C和D之间,矩阵链被分为(ABC)和(D);
在第一步中分出的(A)已经不能在加括号了,所以结束;
而(BCD)继续按照上面的步奏把括号依次加在B和C、C和D之间,其他情况相同。
加括号的过程是递归的。

程序实现

[cpp]  view plain ?
  1. //m数组内存放矩阵链的行列信息  
  2. //m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  3. int Best_Enum(int m[], int left, int right)  
  4. {  
  5.     //只有一个矩阵时,返回计算次数0  
  6.     if (left == right)  
  7.     {  
  8.         return 0;  
  9.     }  
  10.   
  11.     int min = INF; //无穷大  
  12.     int i;  
  13.     //括号依次加在第1、2、3...n-1个矩阵后面  
  14.     for (i = left; i < right; i++)  
  15.     {  
  16.         //计算出这种完全加括号方式的计算次数  
  17.         int count = Best_Enum(m, left, i) + Best_Enum(m, i+1, right);  
  18.         count += m[left-1] * m[i] * m[right];  
  19.         //选出最小的  
  20.         if (count < min)  
  21.         {  
  22.             min = count;  
  23.         }  
  24.     }  
  25.     return min;  
  26. }  


备忘录法优化


上图为递归枚举过程,小方块内的1:4代表第1个矩阵至第4个矩阵的完全加括号方式

可以看到黄色方块中有很多重复计算,所以利用备忘录来保存计算结果,在每次进行计算前,
先查表,看是否计算过,避免重复计算。

程序实现

[cpp]  view plain ?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. #define SIZE 100  
  8. #define INF 999999999  
  9.   
  10. int memo[SIZE][SIZE];  
  11.   
  12. //m数组内存放矩阵链的行列信息  
  13. //m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  14. int Best_Memo(int m[], int left, int right)  
  15. {  
  16.     //只有一个矩阵时,返回计算次数0  
  17.     if (left == right)  
  18.     {  
  19.         return 0;  
  20.     }  
  21.   
  22.     int min = INF;  
  23.     int i;  
  24.     //括号依次加在第1、2、3...n-1个矩阵后面  
  25.     for (i = left; i < right; i++)  
  26.     {  
  27.         //计算出这种完全加括号方式的计算次数  
  28.         int count;  
  29.         if (memo[left][i] == 0)  
  30.         {  
  31.             memo[left][i] = Best_Memo(m, left, i);  
  32.         }  
  33.         count = memo[left][i];  
  34.         if (memo[i+1][right] == 0)  
  35.         {  
  36.             memo[i+1][right] = Best_Memo(m, i+1, right);  
  37.         }  
  38.         count += memo[i+1][right];  
  39.         count += m[left-1] * m[i] * m[right];  
  40.         //选出最小的  
  41.         if (count < min)  
  42.         {  
  43.             min = count;  
  44.         }  
  45.     }  
  46.     return min;  
  47. }  
  48.   
  49. int main(void)  
  50. {  
  51.     int m[SIZE];  
  52.     int n;  
  53.     while (scanf("%d", &n) != EOF)  
  54.     {  
  55.         int i;  
  56.         for (i = 0; i < n; i++)  
  57.         {  
  58.             scanf("%d", &m[i]);  
  59.         }  
  60.         memset(memo, 0, sizeof(memo));  
  61.         printf("%d\n", Best_Memo(m, 1, n-1));  
  62.     }  
  63.     return 0;  
  64. }  

备忘录法优化


上图为递归枚举过程,小方块内的1:4代表第1个矩阵至第4个矩阵的完全加括号方式

可以看到黄色方块中有很多重复计算,所以利用备忘录来保存计算结果,在每次进行计算前,
先查表,看是否计算过,避免重复计算。

程序实现

[cpp]  view plain ?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. #define SIZE 100  
  8. #define INF 999999999  
  9.   
  10. int memo[SIZE][SIZE];  
  11.   
  12. //m数组内存放矩阵链的行列信息  
  13. //m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  14. int Best_Memo(int m[], int left, int right)  
  15. {  
  16.     //只有一个矩阵时,返回计算次数0  
  17.     if (left == right)  
  18.     {  
  19.         return 0;  
  20.     }  
  21.   
  22.     int min = INF;  
  23.     int i;  
  24.     //括号依次加在第1、2、3...n-1个矩阵后面  
  25.     for (i = left; i < right; i++)  
  26.     {  
  27.         //计算出这种完全加括号方式的计算次数  
  28.         int count;  
  29.         if (memo[left][i] == 0)  
  30.         {  
  31.             memo[left][i] = Best_Memo(m, left, i);  
  32.         }  
  33.         count = memo[left][i];  
  34.         if (memo[i+1][right] == 0)  
  35.         {  
  36.             memo[i+1][right] = Best_Memo(m, i+1, right);  
  37.         }  
  38.         count += memo[i+1][right];  
  39.         count += m[left-1] * m[i] * m[right];  
  40.         //选出最小的  
  41.         if (count < min)  
  42.         {  
  43.             min = count;  
  44.         }  
  45.     }  
  46.     return min;  
  47. }  
  48.   
  49. int main(void)  
  50. {  
  51.     int m[SIZE];  
  52.     int n;  
  53.     while (scanf("%d", &n) != EOF)  
  54.     {  
  55.         int i;  
  56.         for (i = 0; i < n; i++)  
  57.         {  
  58.             scanf("%d", &m[i]);  
  59.         }  
  60.         memset(memo, 0, sizeof(memo));  
  61.         printf("%d\n", Best_Memo(m, 1, n-1));  
  62.     }  
  63.     return 0;  
  64. }  

动态规划法


以矩阵链ABCD为例
按照矩阵链长度递增计算最优值
矩阵链长度为1时,分别计算出矩阵链A、B、C、D的最优值
矩阵链长度为2时,分别计算出矩阵链AB、BC、CD的最优值
矩阵链长度为3时,分别计算出矩阵链ABC、BCD的最优值
矩阵链长度为4时,计算出矩阵链ABCD的最优值

备忘录法优化


上图为递归枚举过程,小方块内的1:4代表第1个矩阵至第4个矩阵的完全加括号方式

可以看到黄色方块中有很多重复计算,所以利用备忘录来保存计算结果,在每次进行计算前,
先查表,看是否计算过,避免重复计算。

程序实现

[cpp]  view plain ?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. #define SIZE 100  
  8. #define INF 999999999  
  9.   
  10. int memo[SIZE][SIZE];  
  11.   
  12. //m数组内存放矩阵链的行列信息  
  13. //m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  14. int Best_Memo(int m[], int left, int right)  
  15. {  
  16.     //只有一个矩阵时,返回计算次数0  
  17.     if (left == right)  
  18.     {  
  19.         return 0;  
  20.     }  
  21.   
  22.     int min = INF;  
  23.     int i;  
  24.     //括号依次加在第1、2、3...n-1个矩阵后面  
  25.     for (i = left; i < right; i++)  
  26.     {  
  27.         //计算出这种完全加括号方式的计算次数  
  28.         int count;  
  29.         if (memo[left][i] == 0)  
  30.         {  
  31.             memo[left][i] = Best_Memo(m, left, i);  
  32.         }  
  33.         count = memo[left][i];  
  34.         if (memo[i+1][right] == 0)  
  35.         {  
  36.             memo[i+1][right] = Best_Memo(m, i+1, right);  
  37.         }  
  38.         count += memo[i+1][right];  
  39.         count += m[left-1] * m[i] * m[right];  
  40.         //选出最小的  
  41.         if (count < min)  
  42.         {  
  43.             min = count;  
  44.         }  
  45.     }  
  46.     return min;  
  47. }  
  48.   
  49. int main(void)  
  50. {  
  51.     int m[SIZE];  
  52.     int n;  
  53.     while (scanf("%d", &n) != EOF)  
  54.     {  
  55.         int i;  
  56.         for (i = 0; i < n; i++)  
  57.         {  
  58.             scanf("%d", &m[i]);  
  59.         }  
  60.         memset(memo, 0, sizeof(memo));  
  61.         printf("%d\n", Best_Memo(m, 1, n-1));  
  62.     }  
  63.     return 0;  
  64. }  


动归方程:




k为矩阵链断开的位置
d数组存放矩阵链计算的最优值,d[i][j]是以第i个矩阵为首,第j个矩阵为尾的矩阵链的最优值,i > 0
m数组内存放矩阵链的行列信息,m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)

[cpp]  view plain ?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. #define SIZE 100  
  8. #define INF 999999999  
  9.   
  10. int m[SIZE];        //存放矩阵链的行列信息,m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  11. int d[SIZE][SIZE];  //存放矩阵链计算的最优值,d[i][j]为第i个矩阵到第j个矩阵的矩阵链的最优值,i > 0  
  12.   
  13. int Best_DP(int n)  
  14. {  
  15.     //把d[i][i]置为0,1 <= i < n  
  16.     memset(d, 0, sizeof(d));  
  17.   
  18.     int len;  
  19.     //递归计算矩阵链的连乘最优值  
  20.     //len = 1,代表矩阵链由两个矩阵构成  
  21.     for (len = 1; len < n; len++)  
  22.     {  
  23.         int i, j, k;  
  24.         for (i = 1, j = i+len; j < n; i++, j++)  
  25.         {  
  26.             int min = INF; //无穷大  
  27.             for (k = i; k < j; k++)  
  28.             {  
  29.                 int count = d[i][k] + d[k+1][j] + m[i-1] * m[k] * m[j];  
  30.                 if (count < min)  
  31.                 {  
  32.                     min = count;  
  33.                 }  
  34.             }  
  35.             d[i][j] = min;  
  36.         }  
  37.     }  
  38.     return d[1][n-1];  
  39. }  
  40.   
  41. int main(void)  
  42. {  
  43.     int n;  
  44.     while (scanf("%d", &n) != EOF)  
  45.     {  
  46.         int i;  
  47.         for (i = 0; i < n; i++)  
  48.         {  
  49.             scanf("%d", &m[i]);  
  50.         }  
  51.   
  52.         printf("%d\n", Best_DP(n));  
  53.     }  
  54.     return 0;  
  55. }  

备忘录法优化


上图为递归枚举过程,小方块内的1:4代表第1个矩阵至第4个矩阵的完全加括号方式

可以看到黄色方块中有很多重复计算,所以利用备忘录来保存计算结果,在每次进行计算前,
先查表,看是否计算过,避免重复计算。

程序实现

[cpp]  view plain ?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. #define SIZE 100  
  8. #define INF 999999999  
  9.   
  10. int memo[SIZE][SIZE];  
  11.   
  12. //m数组内存放矩阵链的行列信息  
  13. //m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)  
  14. int Best_Memo(int m[], int left, int right)  
  15. {  
  16.     //只有一个矩阵时,返回计算次数0  
  17.     if (left == right)  
  18.     {  
  19.         return 0;  
  20.     }  
  21.   
  22.     int min = INF;  
  23.     int i;  
  24.     //括号依次加在第1、2、3...n-1个矩阵后面  
  25.     for (i = left; i < right; i++)  
  26.     {  
  27.         //计算出这种完全加括号方式的计算次数  
  28.         int count;  
  29.         if (memo[left][i] == 0)  
  30.         {  
  31.             memo[left][i] = Best_Memo(m, left, i);  
  32.         }  
  33.         count = memo[left][i];  
  34.         if (memo[i+1][right] == 0)  
  35.         {  
  36.             memo[i+1][right] = Best_Memo(m, i+1, right);  
  37.         }  
  38.         count += memo[i+1][right];  
  39.         count += m[left-1] * m[i] * m[right];  
  40.         //选出最小的  
  41.         if (count < min)  
  42.         {  
  43.             min = count;  
  44.         }  
  45.     }  
  46.     return min;  
  47. }  
  48.   
  49. int main(void)  
  50. {  
  51.     int m[SIZE];  
  52.     int n;  
  53.     while (scanf("%d", &n) != EOF)  
  54.     {  
  55.         int i;  
  56.         for (i = 0; i < n; i++)  
  57.         {  
  58.             scanf("%d", &m[i]);  
  59.         }  
  60.         memset(memo, 0, sizeof(memo));  
  61.         printf("%d\n", Best_Memo(m, 1, n-1));  
  62.     }  
  63.     return 0;  
  64. }  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值