实现矩阵连乘的动态规划算法

?


1.计算连个矩阵乘积的标准算法:

//标准算法

void MatrixMultiply(int a[][MAXN], int b[][MAXN], int p, int q, int r)

{

int sum[MAXN][MAXN];

memset(sum, 0, sizeof(sum));

?

int i, j, k;

//遍历矩阵a的行

for (k = 0; k < p; k++)

{

//遍历矩阵b的列

for (j = 0; j < r; j++)

{

//对应位置相乘

for (i = 0; i < q; i++)

{

sum[k][j] += a[k][i] * b[i][j];

}

}

}

}

所以A、B两个矩阵相乘的计算量为p*q*r。

?

2. 计算连个矩阵乘积的动态规划算法:

#include
<stdio.h>

#include
<stdlib.h>

#include<Windows.h>

?

#define
MAX 100

?

?

int matrix_chain(int *p, int
n, int **m, int **s)

{


//a[][]最小乘次数


//s[][]最小乘数时的断开点


int i,j,r,k;

?


for (i = 0; i < n; i++) //单一矩阵的最小乘次都置为0

{


m[i][i] = 0;

}

?


for (r = 2; r <= n; r++) //r为连乘矩阵的个数

{


for (i = 0; i <= n-r; i++) //i表示连乘矩阵中的第一个

{

j = i + r -1; //j表示连乘矩阵中的最后一个


m[i][j] = 99999;


for (k = i; k <= j-1; k++) //在第一个与最后一个之间寻找最合适的断开点,注意,这是从i开始,即要先计算两个单独矩阵相乘的乘次

{


int tmp = m[i][k] + m[k+1][j] + p[i]*p[k+1]*p[j+1];


if (tmp < m[i][j])

{


m[i][j] = tmp;


s[i][j] = k;

}

}

}

}


return
m[0][n-1];

}

?

void print_chain(int
i, int
j, char **a,int **s)

{ //递归的方式来把最小乘数的表达式输出

?


if (i == j)

{

printf("%s",a[i]);

}


else

{

printf("(");

print_chain(i,s[i][j],a,s);

print_chain(s[i][j]+1,j,a,s);

printf(")");

}

}

?

int main()

{


//min_part[i][j]存储的是i+1到j+1的最小乘次,因为是从0开始


//min_point[i][j]存储的是i+1到j+1之间最小乘次时的分割点


int *p, **min_part, **min_point;


char **a;


int n = 6,i;


int ret;

?

p = (int *)malloc((n+1)*sizeof(int));

a = (char **)malloc(n*sizeof(char*));

min_part = (int **)malloc(n*sizeof(int *));

min_point = (int **)malloc(n*sizeof(int *));

?


for (i = 0; i < n; i++)

{

min_part[i] = (int *)malloc(n*sizeof(int));

min_point[i] = (int *)malloc(n*sizeof(int));

a[i] = (char *)malloc(n*sizeof(char));

}

?

p[0] = 30; //第一个矩阵的行数

p[1] = 35; //第二个矩阵的行数

p[2] = 15; //……

p[3] = 5; //……

p[4] = 10; //……

p[5] = 20; //第六个矩阵的行数

p[6] = 25; //第六个矩阵的列数

?

a[0] = "A1";

a[1] = "A2";

a[2] = "A3";

a[3] = "A4";

a[4] = "A5";

a[5] = "A6";

?

ret = matrix_chain(p,n,min_part,min_point);

printf("Minest times:%d.\n",ret);

print_chain(0,n-1,a,min_point);

????printf("\n");

?

free(p);

free(min_part);

free(min_point);

free(a);

????system("pause");

?


return 0;

}

?

3.
递归加括号的过程的运算量:

//加括号的过程是递归的。

//m数组内存放矩阵链的行列信息

//m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)

int Best_Enum(int m[], int left, int right)

{

//只有一个矩阵时,返回计算次数0

if (left == right)

{

return 0;

}

?

int min = INF; //无穷大

int i;

//括号依次加在第1、2、3...n-1个矩阵后面

for (i = left; i < right; i++)

{

//计算出这种完全加括号方式的计算次数

int count = Best_Enum(m, left, i) + Best_Enum(m, i+1, right);

count += m[left-1] * m[i] * m[right];

//选出最小的

if (count < min)

{

min = count;

}

}

return min;

}

?

4.
动态规划法和备忘录优化法程序的运算量:

?

//动态规划法

?

int m[SIZE]; //存放矩阵链的行列信息,m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)

int d[SIZE][SIZE]; //存放矩阵链计算的最优值,d[i][j]为第i个矩阵到第j个矩阵的矩阵链的最优值,i > 0

?

int Best_DP(int n)

{

//把d[i][i]置为0,1 <= i < n

memset(d, 0, sizeof(d));

?

int len;

//递归计算矩阵链的连乘最优值

//len = 1,代表矩阵链由两个矩阵构成

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 = d[i][k] + d[k+1][j] + m[i-1] * m[k] * m[j];

if (count < min)

{

min = count;

}

}

d[i][j] = min;

}

}

return d[1][n-1];

}

//备忘录优化法

int memo[SIZE][SIZE];

?

//m数组内存放矩阵链的行列信息

//m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)

int Best_Memo(int m[], int left, int right)

{

//只有一个矩阵时,返回计算次数0

if (left == right)

{

return 0;

}

?

int min = INF;

int i;

//括号依次加在第1、2、3...n-1个矩阵后面

for (i = left; i < right; i++)

{

//计算出这种完全加括号方式的计算次数

int count;

if (memo[left][i] == 0)

{

memo[left][i] = Best_Memo(m, left, i);

}

count = memo[left][i];

if (memo[i+1][right] == 0)

{

memo[i+1][right] = Best_Memo(m, i+1, right);

}

count += memo[i+1][right];

count += m[left-1] * m[i] * m[right];

//选出最小的

if (count < min)

{

min = count;

}

}

return min;

}

?

int main(void)

{

int n;

????int c;

????char ch;

????cout<<"按对应数字选择相应方法:"<<endl;

????cout<<"-------------"<<endl;

????cout<<"1.备忘录方法"<<endl;

????cout<<"2.动态规划法"<<endl;

????cout<<"-------------"<<endl;

????cout<<"请输入数字:";

????cin>>c;

????switch (c)

????{

????case 2:

????????cout<<endl;

????????cout<<"----------动态规划法----------"<<endl;

????????while (scanf("%d", &n) != EOF)

????????{

????????????int i;

????????????for (i = 0; i < n; i++)

????????????{

????????????????scanf("%d", &m[i]);

????????????}

?

????????????printf("%d\n", Best_DP(n));

????????????cout<<"是否继续(y/n)?"<<endl;

????????????cin>>ch;

????????????if(ch == ‘n‘|ch == ‘N‘)

????????????????exit(0);

????????};

????????break;

????case 1:

????????cout<<endl;

????????cout<<"----------备忘录方法----------"<<endl;

????????while (scanf("%d", &n) != EOF)

????????{

????????????int i;

????????????for (i = 0; i < n; i++)

????????????{

????????????????scanf("%d", &m[i]);

????????????}

????????????memset(memo, 0, sizeof(memo));

????????????printf("%d\n", Best_Memo(m, 1, n-1));

????????????cout<<"是否继续(y/n)?"<<endl;

????????????cin>>ch;

????????????if(ch == ‘n‘|ch == ‘N‘)

????????????????exit(0);

????????};

????????break;

????}

return 0;

}


程序运行结果如下:

?

对于矩阵连乘的标准算法,主要计算量在三重循环,总共需要pqr次数乘;

而使用动态规划算法,在计算过程中保存已解决的子问题的答案。每个子问题只计算一次,而在后面需要时只需要检查一下,从而避免大量重复的运算。

备忘录方法与动态规划法方法虽不同但当实验数据一样时运行结果相同,证明实验中使用的两种方法都是正确的。

综上,矩阵连乘的最有次序计算问题可用自顶向下的备忘录算法或自顶向上的动态规划算法在O(n3)计算时间内求解。这两个算法都利用了子问题的重叠性质,节省了计算量,提高了算法的效率。

时间: 2024-10-09 03:40:34

实现矩阵连乘的动态规划算法的相关文章

矩阵连乘问题(动态规划算法)

问题描述: 具体可参考:https://blog.csdn.net/liufeng_king/article/details/8497607 代码如下: #ifndef MATRIX_CHAIN_H #define MATRIX_CHAIN_H void matrix_chain(int *p, int n, int **m, int **s); void traceback(int i, int j, int **s); #endif #include <iostream> #include

01背包问题的动态规划算法

01背包问题我最初学会的解法是回溯法,第一反应并不是用动态规划算法去解答.原因是学习动态规划算法的时候,矩阵连乘.最长公共子串等问题很容易将问题离散化成规模不同的子问题,比较好理解,而对于01背包问题则不容易想到将背包容量离散化抽象出子问题,从情感上先入为主也误以为动态规划算法不是解决01背包问题的好方法,实际上并不是这样的.另外,动态规划算法不对子问题进行重复计算,但是要自底向上将所有子问题都计算一遍,直到计算出最终问题的结果也就是我们要的答案,有点像爬山的感觉. 问题描述:给定n种物品和一背

五种常用算法之二:动态规划算法

动态规划算法: 基本思想: 动态规划算法通常用于求解具有某种最优性质的问题.在这类问题中,可能会有许多可行解.每一个解都对应于一个值,我们希望找到具有最优值的解.动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的.若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次.如果我们能够保存已解决的子问题的答案,而在需要时再找

动态规划算法之滚动数组的求解(C++)

虽然接触动态规划算法已经有一段时间,给一个01背包问题,能够做到一个表格简单粗暴下去,然后求得结果,但心里总觉得对这个算法理解十分不到位,抱着对算法的热爱,网上很多大牛的算法思维实在让我佩服的五体投地.在此讲一讲动态规划中滚动数组的求解方法,算是对这个知识点做一个记录,也希望有写的不妥的地方,大家能不吝赐教. 首先,我们先看看"滚动数组"的例题,大家可以参考http://www.lintcode.com/en/problem/house-robber/ 题意大概就是说:一个盗贼要去偷盗

动态规划算法--01背包问题

基本思想: 动态规划算法通常用于求解具有某种最优性质的问题.在这类问题中,可能会有许多可行解.每一个解都对应于一个值,我们希望找到具有最优值的解.动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解).若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很

动态规划算法之:最长公共子序列 & 最长公共子串(LCS)

1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab") b a b c 0 0 0 a 0 1

动态规划算法求解0,1背包问题

首先我们来看看动态规划的四个步骤: 1. 找出最优解的性质,并且刻画其结构特性: 2. 递归的定义最优解: 3. 以自底向上的方式刻画最优值: 4. 根据计算最优值时候得到的信息,构造最优解 其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下: 一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好.此时,动态规划算法没有任何多余的计算.同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算

关于动态规划算法的总结

动态规划算法.在T大某位老师的书中说就是递推+反复子问题. 动态规划算法的效率主要与反复子问题的处理有关. 典型的题目有 陪审团.最大公共子串问题 1,最大公共子串问题 这个是动态规划的基础题目. 动态规划就是递推和反复子结构. 确定了递推关系后.找到一个能极大地降低反复运算的子结构至关重要.选的好了,时间效率会非常好. 这个问题,最好还是设第一个串为a,长度为n,第二个串为b.长度m.那么最长的子序列长度为f(n,m) 当a[n]=a[m]时 f(n,m)=1+f(n-1,m-1) 否则f(n

动态规划算法解最长公共子序列LCS问题

第一部分.什么是动态规划算法 ok,咱们先来了解下什么是动态规划算法. 动态规划一般也只能应用于有最优子结构的问题.最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似).简单地说,问题能够分解成子问题来解决. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方式计算最优解的值   //此3步构成动态规划解的基础. 由计算出的结果构造一个最优解.   //此步如果只要求计算最优解的值时,可省略. 好,接下来,咱们