算法学习1:背包问题:暴力-记忆-dp

背包问题是常见的动态规划题目,描述如下:

一个背包有一定的承重cap,有N件物品,
每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,
每件物品只能选择要装入背包还是不装入背包,
要求在不超过背包承重的前提下,选出物品的总价值最大。

给定物品的重量w价值v及物品数n和承重cap。请返回最大总价值。

测试样例:

[1,2,3],[1,2,3],3,6

返回:6

暴力实现

针对背包问题,最简单思路是递归,每次有两种选择,装入背包或者不装入背包,继续递归,直到所有物品都递归过了,返回。每个物品有两种选择,放入或者不放入,时间复杂度O(2^n)。递归深度为n,空间复杂度O(n)。

#include <iostream>
#include <vector>
using namespace std;
int recur(vector<int> & w, vector<int>& v, int n, int current_index, int cap, int current_cap, int current_value) {
    if (current_index == n)
        return current_value;
    //装进背包
    int use = 0;
    if ((current_cap + w[current_index]) <= cap)
        use = recur(w, v, n, current_index + 1, cap, current_cap + w[current_index], current_value + v[current_index]);
    //不装进背包
    int no_use = recur(w, v, n, current_index + 1, cap, current_cap, current_value);    return use > no_use ? use : no_use;
}
int max(vector<int> w, vector<int> v, int n, int cap) {
    return recur(w, v, n, 0, cap, 0, 0);
}
int main() {
	//测试1
	//vector<int> w = { 1, 2, 3};
	//vector<int> v = { 1, 2, 3};
	//int res = max(w, v, 3, 6);//6

	//测试2
	//vector<int> w = { 42,25,30,35,42,21,26,28 };
	//vector<int> v = { 261,247,419,133,391,456,374,591 };
	//int res = max(w, v, 8, 297);//2872

	//测试3  用暴力会通不过
	vector<int> w = { 15, 21, 33, 36, 30, 19, 37, 19, 16, 29, 34, 40, 31, 27, 16, 31, 38, 17, 35, 22, 17, 36, 28, 28, 21, 19, 32, 17 };
	vector<int> v = { 523, 612, 359, 90, 79, 630, 437, 83, 520, 463, 262, 44, 354, 531, 150, 186, 642, 122, 93, 383, 125, 70, 271, 519, 76, 368, 46, 278 };
	int res = max(w, v, 28, 394);//6849

	cout << res << endl;
	system("pause");
	return 0;
}


记忆搜索方法

暴力递归中会有大量的重复计算,记忆搜索将其优化,用额外的空间存储下已经递归过的结果,help[i][j]代表了重量限制为j时,从第i个到第n个物品所能获取到的最大价值。递归时help[i][j]重复使用到的次数越多,复杂度越低。

int recur(vector<int> & w, vector<int>& v, int n, int current_index, int cap, int **help) {
    if (current_index == n)
        return 0;

    //装进背包
    int use = 0;
    int cap_remain = cap - w[current_index];
    if (cap_remain >= 0) {
        use = v[current_index];
        if (help[current_index + 1][cap_remain] == -1)
            help[current_index + 1][cap_remain] = recur(w, v, n, current_index + 1, cap_remain, help);
        use += help[current_index + 1][cap_remain];
    }
    //不装进背包
    if (help[current_index + 1][cap] == -1)
        help[current_index + 1][cap] = recur(w, v, n, current_index + 1, cap, help);
    int no_use = help[current_index + 1][cap];

    //返回较大值
    return use > no_use ? use : no_use;
}
int max(vector<int> w, vector<int> v, int n, int cap) {
    //help二维数组存下递归过的结果
    int ** help = new int*[n+1];
    for (int i = 0; i < n+1; i++) {
        help[i] = new int[cap + 1];
    }
    for (int i = 0; i < n+1; i++) {
        for (int j = 0; j < cap+1; j++)
            help[i][j] = -1;
    }
    return recur(w, v, n, 0, cap, help);
} 


动态规划

从记忆搜索方法进一步发现状态转移的规律,得到动态规划方法。

时间复杂度O(n*cap),空间复杂度O(n*cap)。

int max(vector<int> w, vector<int> v, int n, int cap) {
    int ** dp = new int *[n];
    for (int i = 0; i < n; i++) {
        dp[i] = new int[cap + 1];
    }

    for (int i = 0; i < n; i++) {
        dp[i][0] = 0;
    }
    for (int i = 0; i < cap + 1; i++) {
        if (i >= w[0])
            dp[0][i] = v[0];
        else
            dp[0][i] = 0;
    }

    //dp[i][j]代表重量限制为j时,前i个物品所能达到的最大价值
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < cap + 1; j++) {
            //物品重量小于背包重量时,选择将物品装入,或者不装入,比较两者哪个能获得最大价值
            if (j >= w[i] && dp[i - 1][j] < (dp[i - 1][j - w[i]] + v[i]))
                dp[i][j] = dp[i - 1][j - w[i]] + v[i];
            else
                dp[i][j] = dp[i - 1][j];
        }
    }
    return dp[n - 1][cap];
}

总结

对于动态规划的题目,先把暴力搜索方法写出来,尝试观察其规律,找出动态规划的规律。但往往动态规划的思路比较难想出来,这时候观察递归重复计算的情况,将其优化为记忆搜索,也能使其通过大部分的测试了。


 

猜你喜欢

转载自blog.csdn.net/qq_34881718/article/details/81630283