【动态规划】背包问题-01背包详解(二维数组理解&一维数组优化)

本文详细阐述了01背包问题的基本概念、状态转移方程、动态规划解决方案,并通过实例演示了如何利用一维数组优化计算过程。核心在于理解物品选择策略与背包容量的关系,以求解背包中物品价值最大化问题。

        背包问题有部分背包问题、01背包问题、完全背包问题、多重背包问题以及混合背包问题等几种,其中01背包是最为基础的,理解全了01背包问题,其他的背包问题都迎刃而解。

问题描述

N物品和一个容量为M的背包,设i物品的费用(体积\重量)w[i]价值c[i]。求解如何选择物品可以使得费用(体积\重量)总和不超过背包容量V,且价值最大。

01背包的特点:每种物品只有一件,可以选择放不放

01背包状态分析

首先复习一下动态规划的核心思想:将每个状态的最优值记录下来

状态设置:f[i][v]表示i物品放入一个容量为v的背包所获得的最大价值

只需考虑背包容量为v时,物品的放置情况如下(先理思路,图在后面,要耐心看哦):

(1)第i物品的策略(放或不放)

放的状态:就是i-1物品在背包容量v-w[i]时的最优解+当前物品的价值c[i],也就是f[i-1][v-w[i]]+c[i]

不放的状态:就是i-1物品在背包容量同样为v时的最优解,也就是f[i-1][v]

(2)另一种情况:i件物品的费用大于背包容量,根本放不进状态为:f[i-1][v]

01背包状态转移方程

如果第i件物品放入背包中(w[i] <= v)则状态转移方程为:

f[i][v] = max(f[i-1][v], f[i-1][v - w[i] + c[i])

如果第i件物品不能放入背包中(w[i] > v)则状态转移方程为:

f[i][v] = f[i-1][v]

最终的最优解就是f[N][M] (N物品和一个容量为M的背包)

01背包基本思路

阶段数:N件物品的数目,和背包容量M共同确定的

状态:f[i][v]表示i物品(全部物品或者部分物品)放入一个容量为v的背包可以获得的最大价值

边界条件:f[0][v]=0f[i][0]=0没有物品放入最大价值为0

注意这里的v是会变化的v=(0...M)

01背包动态规划基本思路

举一个例子我们一起来分析下:

        假设有分别a、b、c的三件物品,费用分别是2、3、4,价值分别是1、3、5现在给你承重为8的背包,如何让背包里的物品具有最大价值。

未放入物品时背包的最大价值为0​​​​

首先放a物品:

v=1时放a物品的最优解

v=2时放物品a的最优解

所以同理可以得到在背包容量从0~8的时候,物品a放入背包的剩余情况最优解。

 接着放物品b,和放a物品差不多。

 需要注意的是,当背包容量为3的时候:不放物品b的最大价值为f[i][v] = f[i-1][v] = 1;但是放入b的价值为f[i][v] = f[i-1][v-w[i]] + c[i] = 3,所以此时最大价值为3。

当背包容量为8时,放入b后,此时f[2][8]的最大价值为4。

接着继续放物品c。

最后得到在背包容量为8的时候,最大价值为8.

01背包模板题

一个旅行者有一个最多能装M公斤的背包,现在有N件物品,它们的重量分别是W1,W2,......Wn,它们的价值分别为C1C2......Cn求旅行者能获得最大总价值的物品?

输入格式:

第一行:两个整数,M背包容量M<=200,N物品数量N<=30

第二行至N+1行:每行两个整数Wi, Ci表示每个物品的重量和价值

输出格式:

仅一行,一个数表示最大总价值

输入样例:

10  4

2    1

3    3

4    5

7    9

输出样例:

12

模板题解析

1. 确定阶段数

就是N件物品的数目,和背包容量M共同确定的

2. 确定状态

f[i][v]表示前i种物品(全部物品或者部分物品)放入一个容量为v的背包可以获得的最大价值

3. 确定状态转移方程和边界

放的进:f[i][v] = max{f[i-1][v], f[i-1][v-w[i]] + c[i]}

放不进:f[i][v]=f[i-1][v]

边界条件:f[0][v] = 0 , f[i][0] = 0

4. 程序实现

#include <iostream>
#include <algorithm>
using namespace std;

int main(){
    int M, N, w[205], c[205];
    int f[205][205]={};

    cin>>M>>N;

    for(int i = 1; i <= N; i++)
        cin>>w[i]>>c[i];

    for(int i = 1;i <= N; i++){
        for(int v = 1;v <= M; v++){
	        if(v >= w[i]){
                //放的进的时候,比较放与不放,选择最优解
	            f[i][v] = max(f[i-1][v], f[i-1][v-w[i]] + c[i]);  
	        }else{
	            f[i][v]=f[i-1][v];    //物品费用大于背包体积
	        } 
        }
    }
    cout<<f[N][M];   //最终解
    return 0;
}
    

01背包改进解法思路

根据f[v]=max{f[v],f[v-w[i]]+c[i]}i物品可以发现只与前i-1物品的状态有关

放入a物品后,更新一维数组,见下图。

放入b物品后,更新一维数组,见下图。

放入c物品后,更新一维数组,见下图。

可得状态转移方程: f[v]=max(f[v],f[v-w[i]]+c[i])

注意:v值越小越迟被覆盖,由于要被调用运算。即按v倒序运算

01背包动态规划改进解法分析(一维数组)

1、确定阶段数

就是N 件物品的数目,和背包容量 V共同确定

2、确定状态

f[v]表示i物品(全部物品或者部分物品)放入一个容量为v的背包可以获得的最大价值

3、确定状态转移方程和边界

f[v]=max(f[v],f[v-w[i]]+c[i])

边界条件 f[0] = 0

4、程序实现

#include <iostream>
#include <algorithm>

using namespace std;

int main(){
    int N,M,w[105],c[105],f[105]={};   
    //N物品数量 V背包容量 w[k]c[k]存储物品的重量和价值,f[k]存储不同容量的最优状态

    cin>>M>>N;

    for(int i=1;i<=N;i++)
        cin>>w[i]>>c[i];

    for(int i=1;i<=N;i++){
        for(int v=M;v>=1;v--){     //为什么要倒序???
            if(v>=w[i]){
                f[v]=max(f[v],f[v-w[i]]+c[i]);
            }
        }
    }

    cout<<f[M];

    return 0;
}

练习题

给大家提供一个自己搭建的HustOj,目前在逐渐加题中,有兴趣的话可以去看看,搜索自己想要的题目~

网址:http://120.26.142.136/   或者    wmast.cn

### 使用一维数组实现动态规划01背包问题详解 #### 什么是01背包问题01背包问题是经典的动态规划问题之一,其核心在于给定一组物品和一个固定容量的背包,求解能够放入背包的最大价值组合。其中每个物品只能选择一次。 #### 动态规划的核心思想 动态规划是一种通过分解子问题并保存中间结果来解决问题的方法。对于01背包问题,通常会使用二维数组 `dp[i][j]` 来表示前 `i` 个物品在背包容量为 `j` 的情况下的最大价值[^2]。然而,为了节省空间复杂度,可以将其优化一维数组形式。 #### 一维数组的状态转移方程 在一维数组中,状态转移方程如下: \[ \text{dp}[j] = \max(\text{dp}[j], \text{dp}[j-\text{weight}[i]]+\text{value}[i]) \] 这里的逻辑是从后向前遍历背包容量 \( j \),以确保当前物品只被考虑一次[^5]。 #### 实现细节 以下是具体的一维数组实现方法: 1. **初始化** 创建一个大小为 \( \text{bagsize}+1 \) 的数组 `dp` 并全部初始化为 0,因为当背包容量为 0 时,能获得的价值也为 0。 2. **外层循环** 外层循环用于遍历每一个物品,依次处理它们对不同背包容量的影响。 3. **内层循环** 内层循环从大到小遍历背包容量 \( j \),这样可以防止重复利用同一个物品多次。 4. **更新状态** 对于每个物品 \( i \),如果它的重量小于等于当前背包容量 \( j \),则尝试更新 `dp[j]` 的值。 #### 示例代码 以下是一个完整的 Java 实现示例: ```java public class Knapsack { public static void main(String[] args) { int[] weight = {1, 3, 4}; int[] value = {15, 20, 30}; int bagsize = 4; // 初始化 dp 数组 int[] dp = new int[bagsize + 1]; // 遍历每个物品 for (int i = 0; i &lt; weight.length; i++) { // 倒序遍历背包容量 for (int j = bagsize; j &gt;= weight[i]; j--) { dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); } } // 输出最终结果 System.out.println(&quot;最大价值:&quot; + dp[bagsize]); // 打印整个 dp 数组 for (int j = 0; j &lt;= bagsize; j++) { System.out.print(dp[j] + &quot; &quot;); } } } ``` #### 结果解释 上述代码中的 `dp[bagsize]` 即为所求的最大价值。程序运行结束后,可以通过打印 `dp` 数组查看各个容量对应的最优解。 ---
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

【ACGO】我不会C++

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

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

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

打赏作者

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

抵扣说明:

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

余额充值