0/1背包问题(动态规划)

问题描述

有n个重量分别为w1、w2、... 、wn的物品(物品编号为1~n),它们的价值分别为v1、v2、... 、vn,给定一个容量为W的背包。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放入背包中,而且具有最大的价值。

问题分析

  1. 一个物品 不选 ,不能选择部分放入
  2. 选中的物品重量之和 < W
  3. 选中的物品价值 最大

 举例说明

设 W=10,n=5 

物品编号重量价值
126
223
365
454
546

问题求解

我们设一个x数组,用来表示是否选择该物品,例:x1=1,选中1号物品放入背包,背包剩余重量=W-w1;若x1=0,则不选择1号物品,即在2~n号物品中继续选择,背包剩余重量仍为W。因此,在处理x_{i}问题时出现两种状态:

  • 背包中不放入物品i,则x_{i}=0,背包不增加重量和价值,且剩余重量r不变
  • 背包中放入物品i,则x_{i}=1,背包增加重量w_{i}和价值v_{i},剩余重量 r=W-w_{i}

设置一个动态规划数组dp,dp[i ][r]表示背包剩余容量为r(1 \leqslant r \leqslantW),已考虑物品1、2、... 、i(1 \leqslant i \leqslant n)时背包装入物品的最优价值。对应的状态转移方程如下:

  • dp[i][0] = 0 (背包的重量为0,即没容量存放物品,所以物品总价值为0)【边界条件dp[i][0]=0( 1 \leqslant i \leqslant n)】
  •  dp[0][r] = 0 (没有物品可以放入背包,所以物品总价值为0)【边界条件dp[0][r]=0(  1 \leqslant r \leqslantW)】
  • dp[i][r] = dp[i-1][r]  【当 r < w[i] 时(物品i放不下)】例:若背包剩余重量r=2,物品i的重量w[i]=3,则物品i不能放入背包中,所以背包在物品i的情况下的总价值仍然等于背包在物品i-1的情况下的总价值,即背包的剩余重量r及总价值没有改变。(相当于⬆️处理x_{i}问题时的第一种状态)
  • dp[i][r] = max( dp[i-1][r] , dp[i-1][r-w[i]]+v[i] )  【选择 放入物品i :dp[i-1][r-w[i]]+v[i] 和 不放入物品i :dp[i-1][r] 两者中价值较大的一种】 (相当于⬆️处理x_{i}问题时的第二种状态)

 

因此,dp[n][W]便是该问题的最优解 。 

0

1

2

3

4

5

6

7

8

9

10

0

0

0

0

0

0

0

0

0

0

0

0

1

0

0

6

6

6

6

6

6

6

6

6

2

0

0

6

6

9

9

9

9

9

9

9

3

0

0

6

6

9

9

9

9

11

11

14

4

0

0

6

6

9

9

9

10

11

13

14

5

0

0

6

6

9

9

12

12

15

15

15

 回推最优解过程:

  • i=5,r=W=10,dp[5][10] > dp[4][10],则x[5]=1,r=r-w[5]=6
  • i=i-1=4,dp[4][6] = dp[3][6],则x[4]=0
  • i=i-1=3,dp[3][6] = dp[2][6],则x[3]=0
  • i=i-1=2,dp[2][6] > dp[1][6],则x[2]=1,r=r-w[2]=4
  • i=i-1=1,dp[1][4] > dp[0][4],则x[1]=1,r=r-w[1]=2

 所以,最终装入背包的物品为1、2、5,重量为8,总价值为15。

 代码展示

// 0/1背包问题 动态规划

#include <iostream>
#include <iomanip>
#define N 100
using namespace std;

int W;  //背包总重量
int weight[N];  //物体重量
int value[N];  //物体价值
int dp[N][N];  //背包剩余容量
bool x[N];  //记录是否选取该物品,初始值为0

void Solve(int n)
{
    for(int i=0; i<=n; i++)
        dp[i][0] = 0;        //背包重量为0时,无物品可放
    for(int i=0; i<=W; i++)
        dp[0][i] = 0;        //没有物品时,背包可放重量很多也没用
    for(int i=1; i<=n; i++){
        for(int j=1; j<=W; j++){
            if(j<weight[i]) dp[i][j] = dp[i-1][j];
            else dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
        }
    }
}

int main()
{
    cout<<"Input the weight of the backpack:";
    cin>>W;
    int n;
    cout<<"Input number of objects:";
    cin>>n;
    cout<<"Input the weight and value of the object:"<<endl;
    for(int i=1; i<=n; i++)
        cin>>weight[i]>>value[i];

    Solve(n);

    int temp=W;
    int sum=0;
    for(int i=n; i>0; i--){
        if(dp[i][temp] != dp[i-1][temp]){
            x[i] = true;
            temp -= weight[i];
            sum += value[i];
        }
    }

    cout<<"The selected objects are: ";
    for(int i=0; i<=n; i++)
        if(x[i]) cout<<i<<" ";
    cout<<endl<<"The total values are: "<<sum<<endl;

    return 0;
}

运行结果

参考资料:《算法设计与分析》第2版 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cancri e

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值