实验内容
(1)编写程序实现0-1背包问题的递归,备忘录,及动态规划的比较。
(2)画出运行时间与n*C的曲线,并分析原因.
递归代码:
// 递归算法 int Recursive(int C, int weights[], int values[], int n) { if (n == 0 || C == 0) { return 0; } if (weights[n - 1] > C) { return Recursive(C, weights, values, n - 1); } else { return max(Recursive(C, weights, values, n - 1), values[n - 1] + Recursive(C - weights[n - 1], weights, values, n - 1)); } }
备忘录代码:
// 备忘录算法 int Memoization(int C, int weights[], int values[], int n, int** memo) { if (n == 0 || C == 0) { return 0; } if (memo[n][C] != -1) { return memo[n][C]; } if (weights[n - 1] > C) { memo[n][C] = Memoization(C, weights, values, n - 1, memo); } else { memo[n][C] = max(Memoization(C, weights, values, n - 1, memo), values[n - 1] + Memoization(C - weights[n - 1], weights, values, n - 1, memo)); } return memo[n][C]; }
动态规划代码:
// 动态规划算法 int Dynamic(int C, int weights[], int values[], int n) { int** V = (int**)malloc((n + 1) * sizeof(int*)); for (int i = 0; i <= n; i++) { V[i] = (int*)malloc((C + 1) * sizeof(int)); } // 初始化边界条件 for (int i = 0; i <= n; i++) { V[i][0] = 0; } for (int j = 0; j <= C; j++) { V[0][j] = 0; } // 动态规划计算最大价值 for (int i = 1; i <= n; i++) { for (int j = 1; j <= C; j++) { if (weights[i - 1] > j) { V[i][j] = V[i - 1][j]; } else { V[i][j] = max(V[i - 1][j], values[i - 1] + V[i - 1][j - weights[i - 1]]); } } } int max_value = V[n][C]; // 释放动态分配的内存 for (int i = 0; i <= n; i++) { free(V[i]); } free(V); return max_value; }
递归、备忘录、动态规划结果(部分):
递归、备忘录、动态规划运行时间与n*C的对比:
实验结果:由运行结果可视化分析可以得出,当n逐渐增大后,直接插入排序所花费的时间明显高于合并排序的时间。
原因分析:
1)递归:
时间复杂度为 O(2^n),递归算法是最简单的实现方式,但在处理大规模问题时可能会导致指数级的计算复杂度。因为在每个节点,递归算法会调用自身两次,分别处理放入当前物品和不放入当前物品的情况。这种重复计算会导致算法的效率较低,尤其是当问题规模较大时。因此,随着n和C的增加,递归算法的运行时间呈指数级增长。
2)备忘录:
时间复杂度为 O(nC),通过利用备忘录的记忆功能,避免了重复计算,显著提高了算法的效率。因此,备忘录算法的运行时间相较于递归算法有所减少,并且随着n和C的增加,运行时间的增长速度较为缓慢。
3)动态规划:
时间复杂度为 O(nC),按照自底向上的方式计算子问题的解,并逐步构建出整个问题的解。相对于递归和备忘录算法,动态规划算法具有更高的效率和更小的时间复杂度。随着n和C的增加,动态规划算法的运行时间增长速度较为稳定,呈线性或近似线性的关系。
源码:
//最长公共子序列递归备忘录,动态规划算法比较 #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] = ' '; } 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 ", lcsLengthMemoization); printf("递归备忘录方法运行时间: %f seconds ", (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 ", lcsLengthDynamic); printf("动态规划方法运行时间: %f seconds ", (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 ",timeMemoization - timeDummy); printf(" "); return 0; } #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <time.h> int max(int a, int b) { return (a > b) ? a : b; } // 递归算法 int Recursive(int C, int weights[], int values[], int n) { if (n == 0 || C == 0) { return 0; } if (weights[n - 1] > C) { return Recursive(C, weights, values, n - 1); } else { return max(Recursive(C, weights, values, n - 1), values[n - 1] + Recursive(C - weights[n - 1], weights, values, n - 1)); } } // 备忘录算法 int Memoization(int C, int weights[], int values[], int n, int** memo) { if (n == 0 || C == 0) { return 0; } if (memo[n][C] != -1) { return memo[n][C]; } if (weights[n - 1] > C) { memo[n][C] = Memoization(C, weights, values, n - 1, memo); } else { memo[n][C] = max(Memoization(C, weights, values, n - 1, memo), values[n - 1] + Memoization(C - weights[n - 1], weights, values, n - 1, memo)); } return memo[n][C]; } // 动态规划算法 int Dynamic(int C, int weights[], int values[], int n) { int** V = (int**)malloc((n + 1) * sizeof(int*)); for (int i = 0; i <= n; i++) { V[i] = (int*)malloc((C + 1) * sizeof(int)); } // 初始化边界条件 for (int i = 0; i <= n; i++) { V[i][0] = 0; } for (int j = 0; j <= C; j++) { V[0][j] = 0; } // 动态规划计算最大价值 for (int i = 1; i <= n; i++) { for (int j = 1; j <= C; j++) { if (weights[i - 1] > j) { V[i][j] = V[i - 1][j]; } else { V[i][j] = max(V[i - 1][j], values[i - 1] + V[i - 1][j - weights[i - 1]]); } } } int max_value = V[n][C]; // 释放动态分配的内存 for (int i = 0; i <= n; i++) { free(V[i]); } free(V); return max_value; } int main() { int n = 20; // 物品数量 int C = 50; // 背包容量 int weights[20]; int values[20]; srand(time(0)); for (int i = 0; i < n; i++) { weights[i] = rand() % 10 + 1; // 随机生成物品的重量 values[i] = rand() % 10 + 1; // 随机生成物品的价值 } printf("n C Recursive Memoization Dynamic "); for (int i = 1; i <= n; i++) { int** memo = (int**)malloc((i + 1) * sizeof(int*)); for (int j = 0; j <= i; j++) { memo[j] = (int*)malloc((C + 1) * sizeof(int)); for (int k = 0; k <= C; k++) { memo[j][k] = -1; } } clock_t start, end; clock_t start, end; double cpu_time_used1 = 0; double cpu_time_used2 = 0; double cpu_time_used3 = 0; start = clock(); Recursive(C, weights, values, i); end = clock(); cpu_time_used1 += ((double)(end - start)) / CLOCKS_PER_SEC; start = clock(); Memoization(C, weights, values, i, memo); end = clock(); cpu_time_used2 += ((double)(end - start)) / CLOCKS_PER_SEC; start = clock(); Dynamic(C, weights, values, i); end = clock(); cpu_time_used3 += ((double)(end - start)) / CLOCKS_PER_SEC; printf("%d %d %f %f %f ", i, C, cpu_time_used1 / 3.0, cpu_time_used2 / 3.0, cpu_time_used3 / 3.0); for (int j = 0; j <= i; j++) { free(memo[j]); } free(memo); } return 0;