动态规划背包问题

Posted Vincent丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划背包问题相关的知识,希望对你有一定的参考价值。

  背包问题无疑是最经典的dp问题,其次就是关于字符串匹配问题,数组最长递增(减)序列长度等等。背包问题变体很多。

  动态规划问题实际上与备忘录式深搜有些类似。

1. 0-1背包

题目:

  有n个重量和价值分别为wi, vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

限制条件:

1<= n <= 100; 1<= wi, vi <= 100; 1 <= W <= 1000

样例:

输入

n = 4
(w, v) = {(2,3),(1,2),(3,4),(2,2)}
W = 5

输出

7 (选择第0,1,3号物品)

  先从深搜开始,仔细分析问题,就会发现一个特点:每种物品有两种选择,放或者不放。

int n, int W;
int w[MAX_N], v[MAX_N];
int process(int i, int j){
    int res;
    if(i == n){
        res = 0;
    } else if(j < w[i]){
        res = process(i + 1, j);
    } else {
        res = max(process(i + 1, j), process(i + 1, j - w[i]) + v[i]);
    }
    return res;
}
void solve(){
    printf("%d\n", process(0, W));
}

  

  深搜的一个缺点就是会重复计算,所以有了备忘录式深搜,剪枝操作减少不必要的计算。

int n, int W;
int w[MAX_N], v[MAX_N];
int dp[MAX_N][MAX_W + 1];

int process(int i, int j){
    if(dp[i][j] >= 0) {
        return dp[i][j];
    }  
    int res;
    if(i == n){
        res = 0;
    } else if(j < w[i]){
        res = process(i + 1, j);
    } else {
        res = max(process(i + 1, j), process(i + 1, j - w[i]) + v[i]);
    }
    return dp[i][j] = res;
}
void solve(){
  memset(dp, -1, sizeof(dp));
  printf("%d\n", process(0, W));
}

  

  还有一种深搜写法:

int process(int i, int j, int sum){
    int res;
    if(i == n){
        res = sum;
    } else if(j < w[i]){
        res = process(i + 1, j, sum);
    } else {
        res = max(process(i + 1, j, sum), process(i + 1, j - w[i], sum + v[i]);
    }
    return res;
}

  

  这种写法不利于备忘录式搜索的实现,尽量不要用这种形式。

  根据备忘录式深搜,dp[i][j]为从第i个物品开始挑选总重小于j时,总价值的最大值。

  i = n;  dp[n][j] = 0; 

  j < w[j]     dp[i + 1][j]

    其他    max(dp[i + 1][j], dp[i + 1][j - w[i] + v[i])

void solve(){
    for (int i = n - 1; i >= 0; --i){
        for(int j = 0; j <= W; ++j){
            if(j < w[i]){
                dp[i][j] = dp[i + 1][j];
            } else {
                dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
            }
        }
    }
    printf("%d\n", dp[0][W]);
}

 

  重新定义dp方式:dp[i + 1][j] 为 从前面i个物品中选出总重量不超过j的物品时总价值的最大值。

  dp[0][j] = 0;

  dp[i + 1][j] =  dp[i][j]            j < w[i]

        max(dp[i][j], dp[i][j - w[i]] + v[i])    其他

  

void solve(){
    for(int i = 0; i < n; ++i){
        for(int j = 0; j <= W; ++j){
            if(j < w[i]){
                dp[i + 1][j] = dp[i][j];
            } else {
                dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
            }
        }
    }
    printf("%d\n", dp[n][W]);
}

  

  背包问题的基本方程式: dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);

  动态规划只与之前的状态有关,不会和下一个状态有联系。对于此问题的状态定义,dp[i + 1][j]为将前i件物品放入容量为j的背包,即问题的子问题。若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前i - 1件物品相关的问题。如果不放第i件物品,那么问题就转化为“前i - 1件物品放入容量为j的背包中”,价值为dp[i - 1][j];如果放第i件物品,那么问就转化为“前i - 1件物品放入剩下的容量为j - w[i] 的背包中, 然后将第i件物品放入背包中”,此时能获得的最大价值就是dp[i][j - w[i]]再加上通过放入第i件物品获得的价值v[i],即dp[i][j - w[i]] + v[i] 。

  0-1背包问题解题思路就是如此,但是在具体的代码实现中可以优化代码。

  空间复杂度优化:分析dp方程:dp[i+1][a]的计算只来自dp[i][b](a >= b),那么就联想到数组复用。使用一维数组实现代码。dp[i + 1][j]由dp[i][j]和dp[i][j - w[i]]两个子问题推导得到,所以要保证在推dp[i + 1][j]时能够取用dp[i][j]和dp[i][j - w[i]]的值。也就是说,推导dp[j]时要使用上一循环的dp[j]和dp[j - w[i]],那么就必须保证在本次循环推导dp[j]时不能改写dp[j]和dp[j - w[i]]。

int dp[MAX_W + 1];
void solve(){
    for(int i = 0; i < n; ++i){
        for(int j = W; j >= w[i]; --j){
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    printf("%d\n", dp[n][W]);
}

  

  这里dp[j - w[i]]对应着原来的dp[i][j - w[i]],如果将j的循环顺序颠倒,即w[i]-W,那么就成了dp[i+1][j]由dp[i][j]推导得到,与题意不符。

 

以上是关于动态规划背包问题的主要内容,如果未能解决你的问题,请参考以下文章

分别用回溯法和动态规划求0/1背包问题(C语言代码)

背包动态规划输入

动态规划问题3--多重背包

动态规划问题3--多重背包

动态规划背包问题总结

动态规划之01背包问题(含代码C)