实验内容
(1)最长公共子序列递归备忘录,动态规划算法比较
(2)画出程序运行时间与n*m的关系图。
(3)阐述比较结果。
算法
递归备忘录
int memoizationLCS(char* x, char* y, int i, int j, int** memo) {
if (i == 0 || j == 0) {
return 0;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
if (x[i - 1] == y[j - 1]) {
memo[i][j] = memoizationLCS(x, y, i - 1, j - 1, memo) + 1;
}
else {
memo[i][j] = max(memoizationLCS(x, y, i, j - 1, memo), memoizationLCS(x, y, i - 1, j, memo));
}
return memo[i][j];
}
动态规划
int dynamicLCS(char* x, char* y, int m, int n) {
int** L = (int**)malloc((m + 1) * sizeof(int*));
for (int i = 0; i <= m; i++) {
L[i] = (int*)malloc((n + 1) * sizeof(int));
}
// 初始化L数组
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0 || j == 0) {
L[i][j] = 0;
}
else if (x[i - 1] == y[j - 1]) {
L[i][j] = L[i - 1][j - 1] + 1;
}
else {
L[i][j] = max(L[i - 1][j], L[i][j - 1]);
}
}
}
int lcsLength = L[m][n];
// 释放L数组的内存
for (int i = 0; i <= m; i++) {
free(L[i]);
}
free(L);
return lcsLength;
}
运行结果
时间复杂度
实验结果
显而易见,当n*m值增大时,动态规划算法耗时小于递归备忘录算法耗时
原因分析
虽然备忘录算法和动态规划算法都可以解决最长公共子序列(Longest Common Subsequence,LCS)问题,并且时间复杂度都是O(mn),但动态规划算法通常在实际运行中更快。这是因为动态规划算法在计算过程中逐步构建解决方案,避免了备忘录算法中存储和查找备忘录的开销。
动态规划算法的核心思想是将问题分解为子问题,并保存子问题的解,以便在需要时可以重复使用它们,而不是重新计算。通过这种方式,动态规划算法避免了重复计算相同的子问题,从而减少了计算时间。
备忘录算法是一种通过存储先前计算的结果来避免重复计算的优化技术。在解决最长公共子序列问题时,备忘录算法会存储每个子问题的解,并在需要时查找备忘录以获取解。虽然备忘录算法也可以避免重复计算相同的子问题,但它需要额外的存储空间来存储备忘录,并且在查找备忘录时可能会有一定的开销。
遇到的问题
使用C语言进行编程时遇到了一个错误:试图使用一个运行时确定的变量n来声明一个数组arr。
解决方法
C语言的标准规定,数组的大小在编译时就需要已知,它不能是一个运行时才能确定的变量。代码中,这个错误通常表示在声明一个数组时,使用了一个非常大的数值作为其大小,而这个值并不是一个编译时常量。因此,需要将数组大小更改为一个常量。
源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int max(int a, int b) {
return (a > b) ? a : b;
}
// 递归备忘录方法求解最长公共子序列长度
int memoizationLCS(char* x, char* y, int i, int j, int** memo) {
if (i == 0 || j == 0) {
return 0;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
if (x[i - 1] == y[j - 1]) {
memo[i][j] = memoizationLCS(x, y, i - 1, j - 1, memo) + 1;
}
else {
memo[i][j] = max(memoizationLCS(x, y, i, j - 1, memo), memoizationLCS(x, y, i - 1, j, memo));
}
return memo[i][j];
}
// 动态规划方法求解最长公共子序列长度
int dynamicLCS(char* x, char* y, int m, int n) {
int** L = (int**)malloc((m + 1) * sizeof(int*));
for (int i = 0; i <= m; i++) {
L[i] = (int*)malloc((n + 1) * sizeof(int));
}
// 初始化L数组
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0 || j == 0) {
L[i][j] = 0;
}
else if (x[i - 1] == y[j - 1]) {
L[i][j] = L[i - 1][j - 1] + 1;
}
else {
L[i][j] = max(L[i - 1][j], L[i][j - 1]);
}
}
}
int lcsLength = L[m][n];
// 释放L数组的内存
for (int i = 0; i <= m; i++) {
free(L[i]);
}
free(L);
return lcsLength;
}
// 生成随机字符串
void RandomString(char* str, int length) {
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int charsetSize = sizeof(charset) - 1;
srand(time(NULL));
for (int i = 0; i < length; i++) {
int index = rand() % charsetSize;
str[i] = charset[index];
}
str[length] = '\0';
}
int main(int argc, char* argv[]) {
int n = 3000;
char* x = (char*)malloc((n + 1) * sizeof(char));
RandomString(x, n);
int m = 3000;
char* y = (char*)malloc((m + 1) * sizeof(char));
RandomString(y, m);
// 使用递归备忘录方法求解最长公共子序列长度
int** memo = (int**)malloc((n + 1) * sizeof(int*));
for (int i = 0; i <= n; i++) {
memo[i] = (int*)malloc((m + 1) * sizeof(int));
memset(memo[i], -1, (m + 1) * sizeof(int));
}
clock_t start = clock();
int lcsLengthMemoization = memoizationLCS(x, y, n, m, memo);
clock_t end = clock();
printf("最长公共子序列长度: %d\n", lcsLengthMemoization);
printf("动态规划耗时: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
// 释放memo数组的内存
for (int i = 0; i <= n; i++) {
free(memo[i]);
}
free(memo);
// 使用动态规划方法求解最长公共子序列长度
start = clock();
int lcsLengthDynamic = dynamicLCS(x, y, n, m);
end = clock();
//printf("最长公共子序列长度: %d\n", lcsLengthDynamic);
printf("递归备忘录耗时: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
// 释放输入串x和y的内存
free(x);
free(y);
printf("\n");
return 0;
}