基础算法:两步理解基础背包问题

2024-03-14 09:59

本文主要是介绍基础算法:两步理解基础背包问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

动态规划问题中最为典型的问题之一就是背包问题,而0-1背包是其中最为基础的,N个物品具有各自的重量和价值,根据背包可容纳的重量W的限制,求取在此限制之下能装入的物品的最大价值,一般情况下会转化为dp的一个N*W复杂度的问题求解,这篇文章通过简单的示例进行解释和说明。

目录

  • 背包问题
  • 问题描述
  • 状态转移方程
  • 知识点
    • 知识点1: 初始化
    • 知识点2: 问题拆解
  • 模拟实现
  • 总结

背包问题

背包最早可以追溯至1897年由数学家托比亚斯·丹齐格所提出的在如何满足行李不超载的情况下尽可能的向旅行箱中添加最有价值物品的的问题,也就是说如何选择最合适(价值最高)的物品于给定背包(背包能装入的重量有限制)中,这也是背包问题名称的来源,它在1978年由Merkle和Hellman提出,类似的问题在很多领域中都有应用。

问题描述

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。每种物品仅有一件,可以选择放或不放,这也是基础的0-1背包的特点。

状态转移方程

状态转移方程:f[i][v]=max{ f[i-1][v], f[i-1][v-w[i]]+v[i] }

说明:

  • v[]: 存放物品的价值
  • w[]: 存放物品的重量
  • f[i][v]: 在背包容量为v的情况下,放入前i个物品,f[i][v]保存的为此种情况下的最大价值

注意:如果使用下表从0开始的数组表示的时候,在实现的时候v[i]可能会是v[i-1],当然也可以将v[0]不保存内容,从v[1]开始即可根状态转移方程完全一致了。

知识点

使用动态规划法解决基础背包或者0-1背包问题,理解如下两个知识点是关键。觉得有点难以理解可以先结合后文实现示例进行理解。

知识点1: 初始化

初始化内容主要如下三部分:

  • 物品重量:weight[] 通过输入获取
  • 物品价值:value[] 通过输入获取
  • dp数组:根据动态规划的基本套路,初始化dp数组,dp数组参照状态转移方程进行构建即可,可创建如下二维dp数组dp[i][j]
    • i:表示物品个数
    • j:背包容量

第一个知识点集:dp数组的初始化需要注意如下事项

  • 行表示物品个数,列表示背包容量
  • 行总数为物品个数+1,第一行表示物品为零时的dp[0][j]的值,表示没有物品,所以初始值应为0
  • 列总数为输入的背包容量+1,第一列表示背包容量为0的情况下的最大价值,因为背包容量为0,放不进物品,所以初始值也应为0

知识点2: 问题拆解

状态转移方程:f[i][v]=max{ f[i-1][v], f[i-1][v-w[i]]+v[i] }

问题的拆解最重要的就是上述的状态转移方程,对于对于动态规划方法不熟悉的读者可以参考如下说明进行理解,熟悉的可以直接跳过:

第二个知识点集:dp数组的初始化需要注意如下事项

  • f[i][v]为当前的求解,其值为 f[i-1][v]和f[i-1][v-w[i]]+v[i]中大的那个,前者表示不将序号为i的物品放入背包,后者为放入,求解即为在这二者之中择优。
  • f[i-1][v-w[i]]+v[i]表示把需要为i的物品放入的时候的求解,问题拆解为v[j]和i-1个物品的最大价值的和,后者最大容量的可能即为v-w[i],而保存前i-1个物品在最大容量为v-w[i]的情况下的最大价值即保存在f[i-1][v-w[i]]中,这是已经拆解和解决过的问题。
  • 能够放入或者不能放入可以通过v和v[i]的关系来判断,前者如果小于v[i]自然无论如何都放不进去,此时的值也应该为f[i-1][v]

模拟实现

比如此处使用C来模拟简单的背包问题,示例代码如下所示:

#include <stdio.h>#define MAX_N 100
#define MAX_WEIGHT 10000int max(int a, int b) {return a>b?a:b;
}void knapsack(int num, int input_weight) {int weight[MAX_N] = { 0 };int value[MAX_N] = { 0 };int dp[MAX_N][MAX_WEIGHT] = { 0 };for (int i=0; i<num; i++) {scanf("%d",&weight[i]);}for (int i=0; i<num; i++) {scanf("%d",&value[i]);}for (int i=0; i<input_weight+1; i++) {dp[0][i] = 0;}for (int i=0; i<num+1; i++) {dp[i][0] = 0;}for (int i=1; i<=num; i++) {for (int j=1; j<=input_weight; j++) {if (j < weight[i-1]) {dp[i][j] = dp[i-1][j];} else {dp[i][j] = max(dp[i-1][j],value[i-1]+dp[i-1][j-weight[i-1]]);}}}printf("%d\n",dp[num][input_weight]);
}int main(void) {int num=0, input_weight=0;while (scanf("N=%d, W=%d",&num, &input_weight) != EOF) {knapsack(num,input_weight);}
}

可以看到,代码及其简单,简单说明如下:

  • 此部分代码为初始化部分的内容,前文提到过重量weight和价值value的数组是通过输入赋值,dp的首行首列根据具体意义全部设定为0,作为后续链式推导的基础。
    int weight[MAX_N] = { 0 };int value[MAX_N] = { 0 };int dp[MAX_N][MAX_WEIGHT] = { 0 };for (int i=0; i<num; i++) {scanf("%d",&weight[i]);}for (int i=0; i<num; i++) {scanf("%d",&value[i]);}for (int i=0; i<input_weight+1; i++) {dp[0][i] = 0;}for (int i=0; i<num+1; i++) {dp[i][0] = 0;}
  • 这是模拟的0-1背包问题解决的核心代码,可以看到两层的循环,也就是前文所提到的W*N的时间复杂度的来源,此处i-1和状态转移方程不同的原因在于数组使用时下标从0开始,如果从1开始,可保持一致,其余内容在前文的知识点二中都进行了说明。
    for (int i=1; i<=num; i++) {for (int j=1; j<=input_weight; j++) {if (j < weight[i-1]) {dp[i][j] = dp[i-1][j];} else {dp[i][j] = max(dp[i-1][j],value[i-1]+dp[i-1][j-weight[i-1]]);}}}
  • 执行示例
    在这里插入图片描述

总结

本文对于背包问题进行了简单的解释和说明,背包问题有很多变种,可以再此基础上进行进一步的理解。

这篇关于基础算法:两步理解基础背包问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/808050

相关文章

linux生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

Vim使用基础篇

本文内容大部分来自 vimtutor,自带的教程的总结。在终端输入vimtutor 即可进入教程。 先总结一下,然后再分别介绍正常模式,插入模式,和可视模式三种模式下的命令。 目录 看完以后的汇总 1.正常模式(Normal模式) 1.移动光标 2.删除 3.【:】输入符 4.撤销 5.替换 6.重复命令【. ; ,】 7.复制粘贴 8.缩进 2.插入模式 INSERT

2024.6.24 IDEA中文乱码问题(服务器 控制台 TOMcat)实测已解决

1.问题产生原因: 1.文件编码不一致:如果文件的编码方式与IDEA设置的编码方式不一致,就会产生乱码。确保文件和IDEA使用相同的编码,通常是UTF-8。2.IDEA设置问题:检查IDEA的全局编码设置和项目编码设置是否正确。3.终端或控制台编码问题:如果你在终端或控制台看到乱码,可能是终端的编码设置问题。确保终端使用的是支持你的文件的编码方式。 2.解决方案: 1.File -> S

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

vcpkg安装opencv中的特殊问题记录(无法找到opencv_corexd.dll)

我是按照网上的vcpkg安装opencv方法进行的(比如这篇:从0开始在visual studio上安装opencv(超详细,针对小白)),但是中间出现了一些别人没有遇到的问题,虽然原因没有找到,但是本人给出一些暂时的解决办法: 问题1: 我在安装库命令行使用的是 .\vcpkg.exe install opencv 我的电脑是x64,vcpkg在这条命令后默认下载的也是opencv2:x6

问题-windows-VPN不正确关闭导致网页打不开

为什么会发生这类事情呢? 主要原因是关机之前vpn没有关掉导致的。 至于为什么没关掉vpn会导致网页打不开,我猜测是因为vpn建立的链接没被更改。 正确关掉vpn的时候,会把ip链接断掉,如果你不正确关掉,ip链接没有断掉,此时你vpn又是没启动的,没有域名解析,所以就打不开网站。 你可以在打不开网页的时候,把vpn打开,你会发现网络又可以登录了。 方法一 注意:方法一虽然方便,但是可能会有

ps基础入门

1.基础      1.1新建文件      1.2创建指定形状      1.4移动工具          1.41移动画布中的任意元素          1.42移动画布          1.43修改画布大小          1.44修改图像大小      1.5框选工具      1.6矩形工具      1.7图层          1.71图层颜色修改          1

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std