动态规划 01背包问题(简单易懂)

来源:互联网 发布:microsoft画图软件 编辑:程序博客网 时间:2024/05/19 17:56

之前讲过了动态规划的几个例子,分别是动态规划 矩阵连乘问题 和 动态规划 最常公共子序列问题,学习了动态规划的使用,而01背包问题作为动态规划的经典问题,同时对贪心算法也是一个很重要的补充,所以也必须掌握01背包问题的原理和实现。

01背包问题

题目描述:

有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?

分析这个问题,使用动态规划的话,我们首先想得是这个问题能不能分成子问题,然后优化子结构。

这里写图片描述

ps:动态规划自底向上,所以x轴是从 n-> 1,而y轴是从 1->m

看这个图,紫色部分name表示物品的名字,黄色部分weight表示物品的重量,绿色部分value表示物品的价值,蓝色部分表示承重从1到10的背包,白色部分当前承重的背包所能放入的物品的价值的最大值f[i, j]。

这里首先要说一个01背包状态转换方程:

f[i,j] = Max{ f[i+1,j-Wi]+Vi( j >= Wi ), f[i+1,j] }

  1. f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值。
  2. Vi表示第i件物品的价值

这个式子现在不懂没关系,文章下会解释

问题分析

动态规划的要求就是从底向上,最后一行e,e的重量是4,价值为6,因此0-3的背包放不下,4-10的背包放得下,所以0-3背包的价值为0,4-10的背包价值为6。

然后从下往上看第二行,例如d4,表示承重为4的背包所能翻入d,e两个物品的价值的最大值,在这里承重4明显不能放入d,只能放入e,因为d的weight为5,e的weight为4,所以d4的最大值就是放入e时的价值最大值6,以此类推。

再来看a8 = 15,是怎么来的呢?按照公式,求a8则需要求 b6 + Va 和 b8,取其最大值,那么因为动态规划是自底向上的,所以当我们求a哪一行的时候,bcde行都已经求出,因此b6+Va = 15 > b8 = 9,所以a8应该等于15。

这个公式该怎么理解呢?或者说为什么f[i,j] 就一定会等于f[i+1,j-Wi]+Vi 和 f[i+1,j] 的最大值?
我们根据动态规划的思想来思考,使用动态规划,就是因为该问题可以通过分解为子问题的分治思想,求出各个子问题的最优子结构,从而得出我们问题的解。

a8(f[i][j]),表示承重为8的背包放入a-e物品的最大价值,
b6 + Va (f[i+1][j - w[i]] + Vi):表示我有一个承重为6的背包(原承重为8 减去 a的重量2),能放入bcde物品的最大价值,再加上a的重量的最大价值
b8 (f[i+1,j]):表示有一个承重为8的背包,能放入bcde物品的最大价值

这样考虑:我们求a8,用优化子结构的思想,先求预留了a的空间的背包的最大价值,再加上a的价值,不就是我需要求得的背包的最大价值了吗?

  1. 放入a之后,背包剩余的空间就是8-2 = 6,那么背包为6时的放入bcde物品的最大价值是9,再加上a的价值6等于15,得出我们承重为8的背包最大价值是15,但是还需要考虑下面这个原则:
    在同样的背包承重下,如果放入物品i的背包最大价值小于不放入物品i的背包最大价值,我们选择不放入该物品(因为价值都一样,放入了反而占空间,不放此物品我还可以放其它物品)

  2. 因此我们还要考虑不放入物品a的情况即b8 (f[i+1,j]),b8 = 9,明显放入a之后价值为15 大于 不放入时的价值9,所以我们选择放入

总结:
由以上分析可得,a8=15

代码实现

#include<iostream>#include<algorithm>using namespace std;const int n = 5;//表示物品的数量 const int m = 10;//表示背包所能承受的重量,从1-10 int weight[n+1] = {0, 2, 2, 6, 5, 4};//物品的重量,前面0下标为0只是为了让下标对齐 int value[n+1] = {0, 6, 3, 5, 4, 6};//物品的价值 int f[n+1][m+1];//表示能承受重量为j的背包放入1-i物品的最大价值 void package01(){    int i = n, j;    //首先对最底下的进行填充     for(j = 1; j <= m; j++){        if(j < weight[i]){            f[i][j] = 0;        }else{            f[i][j] = value[i];         }    }    //然后对剩下的n-1个物品填充    for(i = n -1; i > 0; i--){        for(j = 1; j <= m; j++){            if(j < weight[i]){                f[i][j] = f[i+1][j];            }else{                f[i][j] = max(f[i+1][j-weight[i]] + value[i], f[i+1][j]);            }        }    }    for(i = 1; i <= n; i++){        for(j = 1; j <= m; j++){            cout << f[i][j] << " ";        }        cout << endl;    }    cout << "承重为10的背包最大价值是:" << f[1][10];}int main(){    package01();}
0 0