自底向上的"迭代实现"
#include<iostream> using namespace std; const int N=7; //p为矩阵链,p[0],p[1]代表第一个矩阵,p[1],p[2]代表第二个矩阵,length为p的长度 //所以如果有六个矩阵,length=7,m为存储最优结果的二维矩阵,s为存储选择最优结果路线的 //二维矩阵 // iteration void MatrixChainOrder(int *p,int m[N][N],int s[N][N],int length) { int n=length-1; int l,i,j,k,q=0; //m[i][i]只有一个矩阵,所以相乘次数为0,即m[i][i]=0; for(i=1;i<length;i++) { m[i][i]=0; } //l表示矩阵链的长度 // l=2时,计算 m[i,i+1],i=1,2,...,n-1 (长度l=2的链的最小代价) for(l=2;l<=n;l++) { for(i=1;i<=n-l+1;i++) { j=i+l-1; //以i为起始位置,j为长度为l的链的末位, m[i][j]=0x7fffffff; //k从i到j-1,以k为位置划分 for(k=i;k<=j-1;k++) { q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; if(q<m[i][j]) { m[i][j]=q; s[i][j]=k; } } } } cout << m[1][N-1] << endl; } // recursion int MatrixChainOrder_recursion(int *p,int m[N][N],int s[N][N],int i, int j) { if (i == j) { return 0; } if (m[i][j] < 0x7fffffff) { return m[i][j]; } for (int k=i; k<j; ++k) { int tmp = MatrixChainOrder_recursion(p,m,s,i,k) + MatrixChainOrder_recursion(p,m,s,k+1,j) + p[i-1]*p[k]*p[j]; if (tmp < m[i][j]) { m[i][j] = tmp; s[i][j] = k; } } return m[i][j]; } void PrintAnswer(int s[N][N],int i,int j) { if(i==j) { cout<<"A"<<i; } else { cout<<"("; PrintAnswer(s,i,s[i][j]); PrintAnswer(s,s[i][j]+1,j); cout<<")"; } } void traceback(int s[N][N],int i,int j) { if(i==j) { cout<<"A"<<i; return; } if(i < s[i][j]) cout<<"("; traceback(s,i,s[i][j]); if(i < s[i][j]) cout<<")"; if(s[i][j] + 1 < j) cout << "("; traceback(s,s[i][j]+1,j); if(s[i][j] + 1 < j) cout << ")"; } void traceback(int s[N][N]) { cout<<"("; traceback(s,1,N-1); cout << ")"; cout << '\n'; } int main() { int p[N]={30,35,15,5,10,20,25}; int m[N][N],s[N][N]; MatrixChainOrder(p,m,s,N); PrintAnswer(s,1,N-1); cout << '\n'; traceback(s); return 0; } int t_main() { int p[N]={30,35,15,5,10,20,25}; int m[N][N],s[N][N]; for (int i=0; i<N; ++i) { for (int j=0; j<N; ++j) m[i][j] = 0x7fffffff; } cout << MatrixChainOrder_recursion(p,m,s,1,6)<<endl; PrintAnswer(s,1,N-1); return 0; }
15125
((A1(A2A3))((A4A5)A6))
练习题:
回文序列
如果一个数字序列逆置之后跟原序列是一样的就称这样的数字序列为回文序列。例如: {1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列, {1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。 现在给出一个数字序列,允许使用一种转换操作: 选择任意两个相邻的数,然后从序列移除这两个数,并用这两个数字的和插入到这两个数之前的位置(只插入一个和)。 现在对于所给序列要求出最少需要多少次操作可以将其变成回文序列。 输入描述 输入为两行,第一行为序列长度n ( 1 ≤ n ≤ 50) 第二行为序列中的n个整数item[i] (1 ≤ iteam[i] ≤ 1000),以空格分隔。 输出描述 输出一个数,表示最少需要的转换次数
输入例子 4 1 1 1 3输出例子 2
解决思路一 使用动态规划去求解此问题,这里我们用 f[i][j] 表示序列i到j之间的序列变为回文序列最少需要操作的次数,使用辅助数组 ans[i][j] 表示序列 a[i] ~ a[j] 之间所有元素的和,因此 a[i] ~ a[j] 变成回文序列有以下三种方法: a[i] ~ a[j] 合并成一个数,需要的操作次数为 j - i; a[i] ~ a[k], a[k+1] ~ a[j] , 且两者数值相等,则操作次数为 (k - i) + (j - (k + 1)) a[i] ~ a[k] 合并成一个数,a[l] ~ a[j] 合并成一个数,且两者数值相等,并且 a[k+1] ~ s[l-1] 已购车回文序列,该操作次数为 f[k+1][l-1], 则总的操作次数为 (k - i) + f[k+1][l-1] + (j - (k + 1)) 。 我们从这当中选取最小的操作次数即为序列 a[i] ~ a[j] 的最小操作次数
#include <iostream> #define MAXLEN 50 using namespace std; int main() { //数组大小定义MAXLEN+1,为了数组元素从1开始,看起来直观些 int n,a[MAXLEN + 1],f[MAXLEN + 1][MAXLEN + 1],ans[MAXLEN + 1][MAXLEN + 1]; cin>>n; for(int i = 1; i <= n; i++) { cin >> a[i]; ans[i][i] = a[i]; } //计算a[i] ~ a[j]之间的和 for(int i = 1; i < n; i++) { for(int j = i + 1; j <= n; j++) { ans[i][j] = ans[i][j-1] + a[j]; } } for(int j = 0; j <= n - 1; j++) { //统计从索引i开始长度为j的序列操作次数 for(int i = 1; i <= n - j; i++) { //a[i] ~ a[i + j]合并为一个数的操作次数 //(i+j) - i f[i][i+j] = j; //a[i] ~ a[k]合并成一个数,a[l] ~ a[i+j]合并成一个数, //a[k + 1][l - 1]已构成回文序列,操作次数为f[k + 1][l - 1] for(int k = i; k <= i + j - 2; k++) { for(int l = k + 2; l <= i+j; l++) { //a[i] ~ a[k]合并的数与a[l] ~ a[i+j]合并的数相等 if(ans[i][k] == ans[l][i+j]) { //操作次数为f[i][i + j]与(k - i) + f[k + 1][l - 1] + ((i + j) - l)两者的较小者 f[i][i + j] = min(f[i][i + j], f[k + 1][l - 1] + k + j - 1); } } } //a[i] ~ a[k], a[k+1]~a[i+j]分别合并成一个数,并且二者合并的数相等 for(int k = i; k <= i+j; k++) { if(ans[i][k] == ans[k + 1][i + j]) { //操作次数为f[i][i + j]与(k - i) + ((i + j) - (k + 1))两者的较小者 f[i][i + j] = min(f[i][i + j], j - 1); } } } } cout << f[1][n] << endl; return 0; }
复杂度分析
-
时间复杂度:上述算法四层循环计算量为:
因此本算法时间复杂度为 O(n4) 。
-
空间复杂度:本方法使用了两个二维辅助数据,因此空间复杂度为 O(n2) 。
解决思路二
分别用 left 和 right 作为头尾指针向中间进行夹逼,每次操作有下面三种情况:
-
left 指向的元素和 right 指向的元素相等,则 left = left + 1,right = right - 1;
-
left 指向的元素小于 right 指向的元素相等,则将 left 指向的元素与 left + 1 元素只和替换 left + 1 的元素,并使 left = left + 1;
-
left 指向的元素大于 right 指向的元素相等,则将 right 指向的元素与 right - 1 元素只和替换 right - 1 的元素,并使 right=right - 1;
#include <iostream> #include <algorithm> using namespace std; #define MAXLEN 50 int main() { int n,a[MAXLEN + 1]; cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; int left = 1, right = n; //操作次数初始化为0 int ans = 0; while(left < right) { if(a[left] == a[right]) { left++; right--; } else if(a[left] < a[right]) { a[left+1] += a[left]; left++; //操作次数加1 ans++; } else { a[right - 1] += a[right]; right++; //操作次数加1 ans++; } } cout << ans << endl; return 0; }
复杂度分析
-
时间复杂度:时间复杂度为 O(n) 。
-
空间复杂度:空间复杂度为 O(1) 。