实验内容:
(1)最长公共子序列递归备忘录,动态规划算法比较。
(2)画出程序运行时间与n*m的关系图,解释实验结果。
递归代码:
// 递归备忘录方法求解最长公共子序列长度
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]);
}
}
}
递归和动态规划运行结果:
最长公共子序列递归备忘录和动态规划n*m(0<n*m<200000)个数时间效率对比:
实验结果:
由运行结果可视化分析可以得出,当n逐渐增大后,递归备忘录所花费的时间略高于动态规划的时间。
原因分析:
1)递归备忘录:
随着字符串长度的增加,程序的运行时间呈指数级增长。这是由于递归备忘录方法的时间复杂度是O(n*m),需要存储大量中间计算结果来减少重复计算,从而提高效率。
2)动态规划:
随着字符串长度的增加,程序的运行时间呈线性增长。这是由于动态规划方法通过填表法计算最长公共子序列,不需要进行递归调用和重复计算,时间复杂度为O(n*m)。
源码:
//最长公共子序列递归备忘录,动态规划算法比较
#define _CRT_SECURE_NO_WARNINGS
#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 = 1220;
char* x = (char*)malloc((n + 1) * sizeof(char));
RandomString(x, n);
int m = 1550;
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);
// 输出n和m的乘积与程序运行时间之间的关系
double timeMemoization = (double)(end - start) / CLOCKS_PER_SEC;
start = clock();
for (int i = 0; i < n * m; i++) {
// do nothing
}
end = clock();
double timeDummy = (double)(end - start) / CLOCKS_PER_SEC;
printf("程序运行时间: %f seconds\n",timeMemoization - timeDummy);
printf("\n");
return 0;
}