0-1背包问题的递归,备忘录,及动态规划的比较。

实验内容

(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;