第三章 动态规划

冷不防 2022-08-01 00:22 311阅读 0赞

学习要点


理解动态规划的概念
掌握动态规划算法的基本要素
(1) 最优子结构性质
(2) 重复子问题性质
掌握设计动态规划算法的步骤
(1) 找出最优解的性质,并刻画其结构特征
(2) 递归地定义最优值
(3) 以自底向上的方式计算最优解
(4) 根据计算最优值时得到的信息构造最优解。

动态规划与分治法类似,都是将原问题划分成若干子问题求解,不同的是,适用于动态规划法解的问题,经分解得到的子问题往往不是互相独立的。并且,为了避免大量重复计算,可以用一个表来记录所有已解决的子问题的答案,不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划的基本思想。


3.1 矩阵连乘问题

矩阵连乘代码:

  1. void matrixMultiply(int **a,int **b,int **c,int ra,int ca,int rb,int cb)
  2. {
  3. int i = 0;
  4. int j = 0;
  5. int k = 0;
  6. // 矩阵不可乘
  7. if (ca != rb)
  8. return;
  9. for (i = 0; i < ra; i++)
  10. {
  11. for (j = 0; j < cb; j++)
  12. {
  13. int sum = 0;
  14. for (k = 0; k < ca; k++)
  15. {
  16. sum += a[i][k]*b[k][j];
  17. }
  18. c[i][j] = sum;
  19. }
  20. }
  21. }

给定 n 个矩阵 {A1,A2,…,An},其中 Ai 与 Ai+1 是可乘的,i = 1,2,3,…,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)

不同的加括号对应着不同的计算次序,不同的计算次序与计算量有密切的关系。
从以下程序可以得出,矩阵的连乘的计算次数等于 ra*ca*cb。以3个矩阵为例:
{A1,A2,A3} {10*100,100*5,5*50}
((A1A2)A3) 计算次数:10*100*5+10*5*50 = 5000+2500 = 7500
(A1(A2A3)) 计算次数:100*5*50+10*100*50 = 25000+50000 = 75000
可见运算次序是多么重要,于是我们需要寻找最优的运算次序使得乘次数最少。
那么如何让矩阵的连乘次数最少呢?

m[i][j] 数组

  j
i   1 2 3 4 5 6
1 0          
2   0        
3     0      
4       0    
5         0  
6           0
由于 i < j 因此求取问号区域的值

  j
i   1 2 3 4 5 6
1 0 ? ? ? ? ?
2   0 ? ? ? ?
3     0 ? ? ?
4       0 ? ?
5         0 ?
6           0
m[i][j] = min{m[i][k]+m[k+1][j]+(pi-1*pk*pj)} i < j,i <= k < j

  j
i   1 2 3 4 5 6
1 0 k ? ? ? ?
2   0 k ? ? ?
3     0 ? ? ?
4       0 ? ?
5         0 ?
6           0
以 m[1][3]为例,依次需要的数据有 m[1][1],m[2][3],m[1][2],m[3][3],看出来需要首先计算列以下内容,然后依次计算。

同理,求取 m[1][4],需要提前知道 m[1][1],m[2][4],m[1][2],m[3][4],m[1][3],m[4][4].

  j
i   1 2 3 4 5 6
1 0 k k ? ? ?
2   0 k k ? ?
3     0 k ? ?
4       0 ? ?
5         0 ?
6           0

由此得到循环的Code

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. void MatrixChain(int *p,int n,int **m,int **s);
  5. int main()
  6. {
  7. int i = 0;
  8. int n = 0;
  9. /* 矩阵维度的数组 N+1 */
  10. int *p = NULL;
  11. /* 矩阵最少乘法次数的数组 (N+1)*(N+1) */
  12. int **m = NULL;
  13. /* 矩阵拐弯的记录数组(N+1)*(N+1) */
  14. int **s = NULL;
  15. scanf("%d",&n);
  16. p = (int *)malloc((n+1)*sizeof(int));
  17. m = (int **)malloc((n+1)*sizeof(int*));
  18. s = (int **)malloc((n+1)*sizeof(int*));
  19. for (i = 0; i < n+1; i++)
  20. {
  21. *(m+i) = (int *)malloc((n+1)*sizeof(int));
  22. *(s+i) = (int *)malloc((n+1)*sizeof(int));
  23. scanf("%d",&(p[i]));
  24. }
  25. /* 计算矩阵相乘次数*/
  26. MatrixChain(p,n,m,s);
  27. free(p);
  28. free(m);
  29. free(s);
  30. return 0;
  31. }
  32. void MatrixChain(int *p,int n,int **m,int **s)
  33. {
  34. int i = 0;
  35. int j = 0;
  36. int k = 0;
  37. for (i = 0; i <= n; i++)
  38. {
  39. m[i][i] = 0;
  40. }
  41. for (j = 1; j <= n; j++)
  42. {
  43. for (i = j-1; i > 0; i--)
  44. {
  45. m[i][j] = m[i+1][j]+p[i-1]*p[i]*p[j];
  46. s[i][j] = i;
  47. for (k = i+1; k < j; k++)
  48. {
  49. if (m[i][j] > m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j])
  50. {
  51. m[i][j] = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
  52. s[i][j] = k;
  53. }
  54. }
  55. }
  56. }
  57. printf("m[1][n] = %d",m[1][n]);
  58. }

发表评论

表情:
评论列表 (有 0 条评论,311人围观)

还没有评论,来说两句吧...

相关阅读

    相关 动态规划

    学习要点 -------------------- 理解动态规划的概念 掌握动态规划算法的基本要素 (1) 最优子结构性质 (2) 重复子问题性质 掌握设计

    相关

    1.产生进程死锁的必要条件:互斥条件;请求和保持条件;不剥夺条件;环路等待条件。 2.在死锁的条件中,不剥夺条件是指进程已获得的资源只能在使用完时有自己释放。 3.在死锁的

    相关 9 动态规划基础

    第9章 动态规划基础 很多同学听到“动态规划”的名称可能会望而生畏,觉得动态规划的问题都很复杂。但其实,动态规划本质依然是递归算法,只不过是满足特定条件的递归算法。