目录
实验目的
实验内容
实验步骤
一、最长公共子序列
二、矩阵连乘
加深对动态规划法的算法原理及实现过程的理解,学习用动态规划法解决实际应用中的最长公共子序列问题和矩阵连乘问题,体会动态规划法和备忘录方法的异同。
一、用动态规划法和备忘录方法实现求两序列的最长公共子序列问题。要求掌握动态规划法思想在实际中的应用,分析最长公共子序列的问题特征,选择算法策略并设计具体算法,编程实现两输入序列的比较,并输出它们的最长公共子序列。
二、用动态规划法和备忘录方法求解矩阵相乘问题,求得最优的计算次序以使得矩阵连乘总的数乘次数最少,并输出加括号的最优乘法算式。
1、最长公共子序列(Longest Common Subsequence,LCS)问题是:给定两个字符序列 和 ,要求找出 A 和 B 的一个最长公共子序列。 例如:,。它们的最长公共子序列。 通过“穷举法”列出 X 的所有子序列,检查其是否为 Y 的子序列并记录最长公共子序列的 长度这种方法,求解时间为指数级的,因此不可取。
2、分析 LCS 问题特征可知,如果 为它们的最长公共子序列,则它们一定 具有以下性质:
(1)若 ,则 ,且 是 和 的最长公共子序列;
(2)若 且 ,则 是 和 的最长公共子序列;
(3)若且 ,则 是 和 的最长公共子序列。
这样就将求 和 的最长公共子序列问题,分解为求解较小规模的子问题:
若 ,则进一步分解为求解两个(前缀)子字符序列 和 的最长公共子序列 问题;
如果,则原问题转化为求解两个子问题,即找出 和 的最长公共子序列与 找出 和 的最长公共子序列,取两者中较长者作为 和 的最长公共子序列。
由此可见,两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列,具有最优子结构性质。
3、令c[i][j]保存字符序列和的最长公共子序列的长度。 由上述分析可得如下递推式:
由此可见,最长公共子序列的求解具有重叠子问题性质,如果采用递归算法实现,会得到一个指数时间算法。因此需要采用动态规划法自底向上求解,并保存子问题的解,这样可以避免重复计算子问题,在多项式时间内完成计算。
4、为了能由最优解值进一步得到最优解(即最长公共子序列),还需要一个二维数组 s[][], 数组中的元素 s[i][j]记录 c[i][j]的值是由三个子问题 c[i-1][j-1]+1,c[i][j-1]和 c[i-1][j]中的哪一个计算得到,从而可以得到最优解的当前解分量(即最长公共子序列中的当前字符),最终构造出最长公共子序列自身。
5、计算最长公共子序列长度,并给出最长公共子序列:
(注意:C 语言中数组下标由 0 开始,而实际数据在一维数组 a、b 和二维数组 c、s 中的存放却是从下标为 1 处开始。)
二维数组 c 和 s 用于动态规划法求解过程中保存子问题的求解结果,一维数组 a 和 b 用于存放两个字符序列,m 和 n 为两个字符序列中实际字符的个数。
#include
#include
#define MAX 1024
using namespace std;
int m,n;
string a,b;
int c[MAX][MAX],s[MAX][MAX];
int LCSLength() {
for(int i=1; i<=m; i++) {
for(int j=1; j<=n; j++) {
if(a[i]==b[j]) {
c[i][j]=c[i-1][j-1]+1;
s[i][j]=1;
} else if(c[i][j-1]>c[i-1][j]) {
c[i][j]=c[i][j-1];
s[i][j]=2;
} else {
c[i][j]=c[i-1][j];
s[i][j]=3;
}
}
}
return c[m][n];
}
void CLCS(int i,int j) {
if(s[i][j]==0) return;
if(s[i][j]==1) {
CLCS(i-1,j-1);
cout << a[i];
} else if(s[i][j]==2) {
CLCS(i,j-1);
} else if(s[i][j]==3) {
CLCS(i-1,j);
}
}
int main() {
cin >> a >> b;
m = a.length();
n = b.length();
a = '0'+a;
b = '0'+b;
for(int i=0; i<=m; i++)
for(int j=0; j<=n; j++)
c[i][j]=0,s[i][j]=0;
cout << "最长公共子序列长度是:" << LCSLength() << endl;
cout << "最长公共子序列为:";
CLCS(m,n);
return 0;
}
7、输入序列 和 作为测试数据, 测试程序是否能够正确运行?输出结果是什么?
8、分析该动态规划算法的两个主要成员函数 int LCSLength()和 void CLCS()的时间复杂性。
1、求解目标 若 n 个矩阵中两个相邻矩阵 和均是可乘的,的维数为 , 的维数为 。求 n 个矩阵 连乘时的最优计算次序,以及对应的最少 数乘次数。 两矩阵相乘 做 次数乘,可得 的结果矩阵。而矩阵连乘 (简记为 A[i:j])求得 的结果矩阵时,采用不同的计算次序,对应的总数乘次数也不同。
2、例如:4 个矩阵连乘 ,其中的维数:50×10,的维数:10×40, 的维数: 40×30,的维数:30×5。有 5 种不同的计算次序:
次序 1: 需要 50×10×40+50×40×30+50×30×5=87500 次
次序 2: 需要 50×10×40+40×30×5+50×40×5=36000 次
次序 3: 需要 10×40×30+50×10×30+50×30×5=34500 次
次序 4: 需要 10×40×30+10×30×5+50×10×5=16000 次
次序 5: 需要 40×30×5+10×40×5+50×10×5=10500 次
3、将二维数组 m[i][j]定义为:计算 A[i:j]所需的最少数乘次数;
二维数组 s[i][j]定义为:计算 A[i:j]的最优计算次序中的断开位置(例如:若计算 A[i: j]的最优次序在 和 之间断开,i≤k 4、当 i=j 时,A[i:j]=Ai是单一矩阵,无须计算,因此 m[i][j]=0; 当 i 5、算法思路 因为计算 m[i][j]时,只用到已计算出的 m[i][k]和 m[k+1][j]。所以首先计算出 m[i][i]=0, i=1,2,……n;然后再根据递归式,按矩阵链长递增的方式依次计算 m[i][i+1],i=1,2,…n-1(矩阵链长度为 2);m[i][i+2],i=1,2,…n-2(矩阵链长度为 3);……则 m[1][n]就是问题 的最优解值(最少数乘次数)。 要构造问题的最优解,根据 s 数组可推得矩阵乘法的次序。从 s[1][n]可知计算 A[1:n] 的最优加括号方式为(A[1:s[1][n]])(A[s[1][n]+1:n])。其中 A[1:s[1][n]]的最优加括号方式 又为(A[1:s[1][s[1][n]]])(A[s[1][s[1][n]]+1:s[1][n]])。……照此递推下去,最终可以确定 A[1:n]的最优完全加括号方式,构造出问题的一个最优解。 6、动态规划法实现的算法提示 (请分别对实例 1: 维数:50×10, 维数:10×40, 维数:40×30, 维数: 30×5 和实例 2: 维数:30×35, 维数:35×15,维数:15×5,维数:5×10;维数:10×20, 维数:20×25 分别求解。)#include