动态规划之0-1背包问题(简单易懂)

来源:互联网 发布:大数据重塑护理价值 编辑:程序博客网 时间:2024/05/26 09:54

动态规划之0-1背包问题

问题描述

现给定n种物品以及一容量为c的背包,物品具有质量和价值两个属性,物品i的质量为wi,价值为vi。问如何选择装入背包的物品,才能使得背包中物品的总价值最大?


问题分析

给定数据为背包容量c = 10, 物品种类n = 5, 物品质量w[n] = {2, 2, 6, 5, 4}, 物品价值v[n] = {6, 3, 5, 4, 6}。
要解决这个问题,只需要以某种形式填写下方这张表即可了解用动态规划解决0-1背包问题的思想。这张表的纵坐标[0, 10]所表示的是背包的容量,横坐标[1, 5]所表示的是物品的种类,为了后续的叙述方便,将表看作一个二维数组,并用小写字母d来表示。

重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 5 4 6

说明

  • d[5][0]所表示的就是第5行第0列的位置,所代表的含义就是当背包容量为0时(只考虑5号物品),所能够形成的最大价值。
  • d[4][1]所表示的就是第4行第1列的位置,所代表的含义就是当背包容量为1时(考虑5号和4号物品),所能够形成的最大价值。
  • d[3][2]所表示的就是第3行第2列的位置,所代表的含义就是当背包容量为2时(考虑5号,4号和3号物品),所能够形成的最大价值。

填表

上文中我们已经知道了表格中空位置所代表的含义,填这张表之前,还有以下两点需要大家知道:
1. 这张表应该是由下至上,由左至右来进行填写的;
2. 对于每一种物品,只对应着两种状态,装入背包和不装入背包;
知道了以上信息后,便可以开始对这张表进行填写了。

  • 因为表的填写是由下至上,由左至右来进行填写,所以第一个填写的位置应该是d[5][0]位置,此时,背包的容量为0(只考虑5号物品),因为5号物品的重量w[5] = 4,容量为0背包不能够容纳重量为4的物品,所以d[5][0]位置所能形成的最大价值为0。
  • 同理,在d[5][1], d[5][2], d[5][3]这几个位置所能形成的最大价值也为0。
    此时表格变成如下所示:
重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 5 4 6 0 0 0 0
  • 当填写到d[5][4]这个位置的时候,我们发现背包的容量可以容纳5号物品了,也就是说这个位置所形成的最大价值为v[5] = 6。
  • 因为第5行只需要考虑5号物品,则第5行剩下的位置同理,都能够容纳5号物品,所以第5行剩下的位置所能形成的最大价值都为v[5] = 6。
    我们填好了表格最下面的一层,此时表格变成如下所示:
重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 5 4 6 0 0 0 0 6 6 6 6 6 6 6

填写完第5行之后,接下来我们来填写第4行:

  • 首先来填写第四行的d[4][0], d[4][1], d[4][2], d[4][3], d[4][4]这5个位置,为什么是这5个位置?这5个位置有一个共同的特点,因为4号物品的重量w[4] = 5, 当背包的容量小于5时,4号物品不能够装入背包。既然4号物品不能装入背包,那么就直接取当前背包容量能形成的最大价值,就拿d[4][4]来说吧,因为此时4号物品不能够装入背包,背包中剩余的容量为4,容量为4时,能得到的最大价值是多少呢?在第5行我们已经得出了结果,为6,也就说从第5行取值到第4行,即d[5][0]–>d[4][0] = 0, d[5][1]–>d[4][1] = 0, d[5][2]–>d[4][2] = 0, d[5][3]–>d[4][3] = 0, d[5][4]–>d[4][4] = 6。
    此时我们将表填成了这样:
重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 0 0 0 0 6 5 4 6 0 0 0 0 6 6 6 6 6 6 6
  • 当填写d[4][5]这个位置的时候,因为4号物品的重量w[4] = 5,背包容量为5,此时4号物品可以被装入背包了。上文中我们曾说过,对于每一种物品,都只对应着两种状态,装入背包和不装入背包,对于目前来说,对应着的两种情况便是将4号物品装入背包和不将4号物品装入背包。

    • 不将4号物品装入背包:
      回顾刚才填写d[4][0]~d[4][4]的填写过程,因为背包容量不能够容纳4号物品,所以没有将4号物品装入背包,直接取的是当前背包容量能形成的最大价值,也就是取第5行的值到第4行。现在背包的容量增大,虽然可以容纳4号物品,但是归根结底仍然是未将其装入背包,所以与前面同理,直接令d[5][5] –> d[4][5] = 6;
    • 将4号物品装入背包:
      将4号物品装入背包,也就是说现在背包中已经装入了4号物品,所剩下的容积为当前容积 (5)– 4号物品的重量(5)。当装入了4号物品,背包中剩余的容积为0,则当期背包中物品的价值为4号物品的价值(4)+容积为0时形成的最大价值(0),即4。
  • 不将4号物品放入背包可以得到的最大价值为6,将4号物品放入背包中可以得到的最大价值为4,则在d[4][5]这个位置,所能形成的最大价值为6。
    此时我们把表填成了这样:

重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 0 0 0 0 6 6 5 4 6 0 0 0 0 6 6 6 6 6 6 6
  • 填到这里,各位读者可能已经发现了一些规律了,当背包容积小于当前行n号物品的质量时,物品不能够装入背包,则取当前背包容积所能形成的最大价值。当背包容积大于等于当前行n号物品的质量时,背包可以容纳物品放入,此时分为两种情况,一种是选择装入此物品(用放入此物品后剩余的容量能形成的最大值加上此物品的价值便是放入此物品形成的最大价值),另一种是不装入此物品(直接取当前背包容量能形成的最大值便是不装入此物品形成的最大价值),两种情况中选择更大的一个。可能说的比较拗口,但是如果把上文填写第4行的思路理解,理解这一大段话还是很简单的。
  • 经过上面的讲解,我们可以试着来推导一下公式:
    • 当容量小于w[i]:d[i][j] = d[i + 1][j];
    • 当容量大于等于w[i]:d[i][j] = max(d[i + 1][j], d[i + 1][j - w[i]] + v[i]);
  • 接下来我们利用公式来继续填写这张表,d[4][6]位置,当前背包的容量为j = 6,4号物品的重量为w[i] = 5,因为6 > 5,则选取第二个公式进行计算:
    1. d[i + 1][j] = d[4 + 1][6] = d[5][6] = 6
    2. d[i + 1][j - w[i]] + v[i] = d[4 + 1][6 - 5] + 4 = d[5][1] + 4 = 4
    3. 1和2中更大的值为6,则[4][6] = 6;
  • 依此类推,可以将第四行剩下部分填写完成,表变成如下所示:
重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 0 0 0 0 6 6 6 6 6 10 10 5 4 6 0 0 0 0 6 6 6 6 6 6 6
  • 接下来我们试着利用第一个公式来填写第3行的前半部分,首先来看d[3][0], 此时背包的容量为0,3号物品的重量为6,因为0 < 6, 则选取第一个公式。

    • d[i + 1][j] = d[3 + 1][0] = d[4][0] = 0;
    • 则d[3][0] = 0;
  • 依次类推,因为3号物品的重量为6,所以d[3][0]~d[3][5]都使用公式1进行计算,第3行剩余的部分使用公式2进行计算,这样便可以完成第3行的填写,填写结果如下:

重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 0 0 0 0 6 6 6 6 6 10 11 4 5 4 0 0 0 0 6 6 6 6 6 10 10 5 4 6 0 0 0 0 6 6 6 6 6 6 6

根据上述的讲解,剩下的第2行和第1行填写起来应该就不难了,请先试着填写一下剩下的两行,再向下进行阅读。


最终表格

通过上述的知识,最终可以得到这样一张表格:

重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 0 0 6 6 9 9 12 12 15 15 15 2 2 3 0 0 3 3 6 6 9 9 9 10 11 3 6 5 0 0 0 0 6 6 6 6 6 10 11 4 5 4 0 0 0 0 6 6 6 6 6 10 10 5 4 6 0 0 0 0 6 6 6 6 6 6 6

通过这张表可以清晰的看出,这组数据可以得到的最大价值为15。


小改进

按照上述的方法,我们已经可以计算出所需要的结果了,但是这里有一个小问题,再填写最下面一行的时候,不能和其他行做到统一,因为它没有下一行,没有办法实现上面公式中的操作,所以,我们可以在这个表下面多加一行,如下所示:

重量w 价值v 0 1 2 3 4 5 6 7 8 9 10 1 2 6 2 2 3 3 6 5 4 5 4 5 4 6 6 0 0 0 0 0 0 0 0 0 0 0 0 0

这样的话,就不用单独写第5行的计算了,每一行的计算都遵循那两个公式即可。


样例代码

#include<iostream>#include<cstdlib>#include<cstring>using namespace std;const int maxn = 20;int d[maxn][maxn];int w[] = {2, 2, 6, 5, 4};int v[] = {6, 3, 5, 4, 6};int main(){    int n = 5, c = 10;    memset(d, 0, sizeof(d));        //清空数组    for(int i = n - 1; i >= 0; i--) //由下至上    {        for(int j = 0; j <= c; j++) //由左至右        {            if(w[i] > j)            //如果w[i] < j, 执行公式1                d[i][j] = d[i+1][j];            else                    //否则,执行公式2            {                int max = d[i+1][j] > (d[i+1][j-w[i]] + v[i]) ? d[i+1][j] : (d[i+1][j-w[i]] + v[i]);                d[i][j] = max;            }        }    }    //输出表    for(int i = 0; i < n; i++)    {        for(int j = 0; j <=c; j++)            cout << d[i][j] << " ";        cout << endl;    }    cout << d[0][c - 1] << endl;    //表格第一行的最后一个值即最大价值    system("pause");    return 0;}
原创粉丝点击