01背包问题的一种动态规划解法

01背包问题的一种动态规划解法

问题描述

给定一个背包,容量(如负重等)为m
给定一组物品共 n n n 个,每种一个,各有价值和重量

123 … \dots n
v[i]v[1]v[2]v[3] … \dots v[n]
w[i]w[1]w[2]w[3] … \dots w[n]

求能放入背包的最大价值

思路

动态规划,实为穷举放入背包各种物品的情况,根据规模更小的子问题递推出更大规模问题的解

建表 dp[N][N]表中值 dp[i][j] 表示将给定的i物品放入一个容量为 j 的背包,所能放入的最大价值

(j=)01234
(i=)000000
10dp[1][1]
20 … \dots
30 … \dots
40dp[4][4]
递推式

d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , w [ i ] > j m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) , w [ i ] < = j dp[i][j]= \begin{cases} dp[i-1][j],\quad w[i]>j\\ max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]), \quad w[i]<=j \end{cases} dp[i][j]={dp[i1][j],w[i]>jmax(dp[i1][j],dp[i1][jw[i]]+v[i]),w[i]<=j

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j] 当前背包总容量 j 不能容纳第i件物品(质量为w[i]),放不进去不必考虑,等同于i-1件物品的情况

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i]) 当前背包总容量 j 能容纳第i件物品(质量为w[i])考虑第i件物品放入和不放入背包两种情况的最优解。

不放入,同i-1件物品的情况;

放入,要保证第i件物品可以放入背包中,即先放入第i件物品。

放入第i件物品后,当前背包的总容量j-w[i],此时的最优解变为i-1物品放入一个容量为 j-w[i] 的背包,所能放入的最大价值 dp[i-1][j-w[i]] 与第i件物品的价值 v[i] 之和。

代码
//01背包问题
//n件物品各有价值和质量,每种1件,求一个容量有限的背包装入的最大价值
#include <iostream>

using namespace std;

const int N = 1024;//
int dp[N][N];//容量为j的背包放入前i件物品时的最大价值dp[i][j]
int v[N];//价值
int w[N];//质量

void maketable(int m, int n)//n件物品,背包总容量为m
{
    for (int i = 0; i <= n; i++) dp[0][i] = 0;
    for (int i = 0; i <= m; i++) dp[i][0] = 0;
    //容量或装入的物品数目为0时价值为0

    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j > 0; j--)
        {
            if (w[i] > j) dp[i][j] = dp[i - 1][j];
            //放不下,不放,和少一件物品等值
            //dp[i][j]=dp[i-1][j],w[i]>j
            else
            {
                int tmp = dp[i - 1][j - w[i]] + v[i];
                //在保证放入背包(i-1)件物品后,剩余空间足够放下第i件物品时的最大价值
                //+第i件物品的价值

                //确定放于不放第i件物品时,容量为m物品为i件的子问题最优解
                //即放于不放第i件物品时,容量为m的背包对于i件物品,所能装入的最大价值
                //即max(dp[i-1][j], dp[i-1][j-w[i]]+w[i])
                if (dp[i - 1][j] < tmp)
                    dp[i][j] = tmp;
                else
                    dp[i][j] = dp[i - 1][j];
            }
        }
    }
}


int main()
{
    int m, n;
    cout << "输入背包容量:";
    cin >> m;
    cout << "输入物品总数:";
    cin >> n;
    cout << "输入物品价值";
    for (int i = 1; i <= n;i++) cin >> v[i];
    cout << "输入物品质量";
    for (int i = 1; i <= n;i++) cin >> w[i];

    maketable(m, n);

    cout << "最大价值为:" << dp[n][m];

    return 0;
}
空间优化

用一维数组存储i-1件物品时各容量的最优解

注意此时内循环反向,防止前一状态的数据被提前覆盖

例:

(j=)012 … \dots m
(i=)0
… \dots … \dots … \dots … \dots … \dots … \dots
i-10 1 i − 1 1_{i-1} 1i1 2 i − 1 2_{i-1} 2i1 … \dots m i − 1 m_{i-1} mi1
i0 1 i 1_i 1i 2 i 2_i 2i … \dots m i m_i mi
… \dots … \dots … \dots … \dots … \dots … \dots

如上表,当dp[i-1][j] != dp[i][j] j i − 1 ≠ j i j_{i-1} \ne j_{i} ji1=ji

(j=)0 … \dots j … \dots m
… \dots … \dots … \dots … \dots … \dots … \dots
i-1次循环0 … \dots j i − 1 j_{i-1} ji1 … \dots m i − 1 m_{i-1} mi1
i次循环0 … \dots j i ≠ j i − 1 j_i\ne j_{i-1} ji=ji1 … \dots m i = j i + v [ i ] j i ≠ d p [ i − 1 ] [ j ] m i ≠ d p [ i − 1 ] [ j ] + v [ i ] m_i=j_i+v[i]\\j_i\ne dp[i-1][j]\\m_i\ne dp[i-1][j]+v[i] mi=ji+v[i]ji=dp[i1][j]mi=dp[i1][j]+v[i]

j=m-w[i]dp[i][m]=dp[1-1][j]+v[i]时,

因正序循环,不能保证一维数组的dp[j]=dp[i-1][j]

即正序循环在j较小时的值dp[j]改变,i-1次循环的状态 dp[i-1][j] 被覆盖

要保证i-1次循环的状态 dp[i-1][j] 在需要使用时不被覆盖,需要内循环反向,即先使用再更新

//内循环反向,确保每次考虑下一件物品时在前一状态的基础上,即dp[j-w[i]]=dp[i-1][j-w[j]]
//若内循环正向,会导致,第二次循环时,dp[j-w[i]]=dp[i][j-w[i]],即上一状态的数据被覆盖
const int N = 1024;//
int dp[N];//容量为j的背包放入前i件物品时的最大价值dp[j],i由第i次循环表示
int v[N];//价值
int w[N];//质量

void maketable(int m, int n)//n件物品,背包总容量为m
{
    for (int i = 0; i <= n; i++) dp[i] = 0;
    //容量或装入的物品数目为0时价值为0

    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j > 0; j--)
        //内循环反向,确保每次考虑下一件物品时在前一状态的基础上,即dp[j-w[i]]=dp[i-1][j-w[j]]
		//若内循环正向,会导致,第二次循环时,dp[j-w[i]]=dp[i][j-w[i]],即上一状态的数据被覆盖
        {
            if (w[i] > j) ;
            //放不下,不放,和少一件物品等值
            //dp[i][j]=dp[i-1][j],w[i]>j
            //与上一状态一致,即保持dp[j]不变
            else
            {
                int tmp = dp[j - w[i]] + v[i];
                //在保证放入背包(i-1)件物品后,剩余空间足够放下第i件物品时的最大价值
                //+第i件物品的价值

                //确定放于不放第i件物品时,容量为m物品为i件的子问题最优解
                //即放于不放第i件物品时,容量为m的背包对于i件物品,所能装入的最大价值
                //即max(dp[i-1][j], dp[i-1][j-w[i]]+w[i])
                if (dp[j] < tmp)
                    dp[j] = tmp;
                else
                    ;
            }
        }
    }
}

如有不足,还请指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值