详解动态规划01背包问题--JavaScript实现

news2024/9/23 1:40:53

对其他动态规划问题感兴趣的,也可以查看

详解动态规划最少硬币找零问题--JavaScript实现

详解动态规划最长公共子序列--JavaScript实现

一开始在接触动态规划的时候,可能会云里雾里,似乎能理解思路,但是又无法准确地表述或者把代码写出来。本篇将一步一步通过作图的方式帮助初次接触动态规划的同学来理解问题。这一篇将以经典的 01背包 问题为例子来讲解,最后通过纯 JavaScript 来实现,在 Sublime 上运行演示。当然如果不会 JavaScript 也一点关系都没有,因为最重要的是理解整个推导过程。在语言实现的时候,也没有涉及什么语言特性,基本上懂个C语言就能看懂了。

问题

给定一个固定大小的背包,背包的容量为 capacity,有一组物品,存在对应的价值和重量,要求找出一个最佳的解决方案,使得装入背包的物品总重量不超过背包容量 capacity,而且总价值最大。本题中给出了3个物品,其价值和重量分别是 (3,2),(4,3),(5,4)。括号左边为价值,右边为重量,背包容量 capacity 为5。那么求出其搭配组合,使得背包内总价最大,且最大价值为多少?

分析

在开始计算之前,需要先对动态规划中的01背包问题有基本的理解:

  • 物品无法拆成分数形式,如果能拆分,那就属于贪婪算法问题,在后面的文章我们也会介绍贪婪算法。

  • 不一定恰好装满背包。

  • 装满时总价值不一定最大。

  • 每样物品各一件

  • 常规情况下,在表格中,价格和重量一般都是从上到下递增的。我们填表分析的时候,其实是事先默认了这种递增的关系。

清楚了上面的原则之后,就可以开始进行分析了。

当一个新物品出现的时候,需要去决策如果选择了它,是否会让总价值最大化。我们根据问题,建立如下表格用于分析:

我们对这个表格做一下说明,左上角 val 和 w 分别是物品的价值和重量。即上面所描述的3个物品的价值与重量对应关系。

从第三列到最后一列,使用了变量 j,它表示背包总容量,最大值为5,也就是前面问题所说的 capacity 的值。

第二行到最后一行,使用 i 表示,下标从0开始,一共有3个物品,所以 i 的最大值为 2。即我们使用i表示物品,在下面介绍中将i=0称为物品0,i=1称为物品1,以此类推。

除了 j = 0 的情况以外,我们将从左到右,从上到下一步一步去填写这个表格,来找到最大的价值。

表格中未填写的空格,表示背包内物品总价值。我们后面将使用 T[i][j] 二维数组来表示它。

1. 总容量为0的情况

如果背包总容量为0,那么很显然地,任何物品都无法装进背包,那么背包内总价值必然是0。所以第一步先填满 j=0 的情况。

2. 第0行,i = 0 的空格分析

正如上面所说,我们接下来将从上到下,从左往右地填写这个表格。所以现在把注意力定位到 i =0, j = 1 的空格上。

在分析过程中,有一个重要原则:分析第i行时,它的物品组合仅能是小于等于i的情况。

怎么理解这个原则:比如分析i=0这一行,那么背包里只能装入物品0,不能装入其他物品。分析i=1这一行,物品组合可以是物品0物品1

i=0 j=1 : 背包总容量为1,但是物品0 的重量为 2,无法装下去,所以这一格应该填 0

i=0 j=2 : 背包总容量为2,刚好可以装下物品0 ,由于物品0 的价值为3,因此这一格填 3

i=0 j=3 : 背包总容量为3,由于根据上面说明的物品组合原则,第0行,仅能放物品0,不需要考虑物品1 和 物品2,所以这一格填 3

i=0 j=4 : 同理,填 3

i=0 j=5 : 同理,填 3

这样我们可以完成第0行的填写,如下图:

3. 第1行,i = 1 的空格分析

在这一行,可以由物品0 和物品1 进行自由组合,来装入背包。i=1 j=1 : 背包总容量为1,但是物品0 的重量为 2,物品1重量为3,背包无法装下任何物品,所以填 0

i=1 j=2 : 背包总容量为2,只能装下物品0,所以填 3

i=1 j=3 : 背包总容量为3,这时候可以装下一个物品1,或者一个物品0,仅仅从人工填表的方式,很容易理解要选择物品1,但是我们该如何以一个确切的逻辑来表达,让计算机明白呢?基于上面说说明的价值和重量在表格中从上到下递增原则,可以确认物品1的价值是大于物品0的,所以默认情况下优先考虑物品1,当选择了物品1之后,把背包剩余的容量和物品1之前的物品重量对比(也就是和物品0的重量对比,如果剩余重量能装下前面的物品,那么就继续装)。所以这里选择物品1,填 4

i=1 j=4 : 选择了物品1之后,物品1 的重量为3,背包容量为4, 减去物品1的重量后, 剩余容量为1,无法装下物品0,所以这里填 4

i=1 j=5 选择了物品1之后,剩余的容量为2,刚好可以装下物品0,所以一格背包装了物品1,和物品 0,总价值为7,把 7 填入表格。

这样我们就完成了第二行的填写,如下图:

3. 第2行,i = 2 的空格分析

i=2 j=1 : 填 0

i=2 j=2 : 填写这一行时,3种物品都有机会被装入背包。总容量为2时,只能装物品0,所以填 3

i=2 j=3 : 物品2的重量为4,大于容量j,所以这里可以参考 T[i-1][j]的值,也就是 i=1 j=3那一格的值,填 4

i=2 j=4 : 可以装下物品2,价值为5。也可以装下物品1。这一空格需要谨慎一点。我们将使用更严谨的方式来分析。在i=1 j=5 中出现了物品组合一起装入背包的情况,这一空将延续这种分析方式。我们选择了物品2,剩余的容量表达式应为 j-w[i]4 - 4 = 0,剩余的容量用于上一行的搜索,由于上一行我们是填写完的,所以可以很轻易地得到这个值。表达式可以写成 val[i] + T[i-1][j-w[i]] ,可以根据这个表达式得出一个值。但是这并不是最终结果,还需要和上一行同一列数值对比,即 T[i-1][j],对比,取最大值。最后这里填 5

i=2 j=5 : 根据上面计算原理,这里如果选择了物品2,那么最大价值只能5,参照上一行,同一列,价值为7,取最大值。所以放弃物品2,选择将物品0和物品1装入背包,填写7。

完成后的表格如下:

伪代码表达

理解了上面整个填表过程,我们要把逻辑抽取出来,在具体代码实现之前,先用伪代码表达出来。

if(j < w[i]){ //容量小于重量,hold不住
	T[i][j] = T[i-1][j]; //所以值等于上一行,同一列。如果i=0,没有上一行,则T[i][j] 取0
}else{
	T[i][j] = max(val[i] + T[i-1][j-w[i]] , T[i-1][j]);  //参照上面 i=2 j=4 和 i=2 j=5 时的填表分析
}
复制代码

以上这简短的伪代码就是解决问题的核心思路,可以应用于任何你熟悉的编程语言上。

说到这里,这篇文章应该是要基本告一段落了。

JavaScript 实现

如果你已经理解了上面的填表分析和伪代码表达,那么就可以尝试着自己去用代码实现了。最后放出在使用 JavaScript的一种实现方式供大家参考,不再针对针对代码做太多说明,重要区域会有注释。如果 Sublime 支持纯 JavaScript,可以直接复制黏贴,command+b 运行看结果。

function knapSack(w,val,capacity,n){
    var T = []

    for(let i = 0;i < n;i++){
        T[i] = [];
        for(let j=0;j <= capacity;j++){
            if(j === 0){ //容量为0
                T[i][j] = 0;
                continue;
            }    
            if(j < w[i]){ //容量小于物品重量,本行hold不住
                if(i === 0){
                    T[i][j] = 0; // i = 0时,不存在i-1,所以T[i][j]取0

                }else{
                    T[i][j] = T[i-1][j]; //容量小于物品重量,参照上一行

                }
                continue;
            }
            if(i === 0){
                T[i][j] = val[i]; //第0行,不存在 i-1, 最多只能放这一行的那一个物品
            }else{
                T[i][j] = Math.max(val[i] + T[i-1][j-w[i]],T[i-1][j]);

            }
        }

    }

    findValue(w,val,capacity,n,T);


    return T;
}


//找到需要的物品
function findValue(w,val,capacity,n,T){

    var i = n-1, j = capacity;
    while ( i > 0 && j > 0 ){

        if(T[i][j] != T[i-1][j]){

            console.log(i,j,'选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);
            j = j- w[i];
            i--;
        }else{
            i--;  //如果相等,那么就到 i-1 行
        }
    }
    if(i == 0 ){
        if(T[i][j] != 0){ //那么第一行的物品也可以取
            console.log(i,j,'选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);

        }
    }
}

// w = [2,3,4].  val = [3,4,5] , n = 3 , capacity = 5
//function knapSack([2,3,4],[3,4,5],5,3);
// 
var values = [3,4,5],
    weights = [2,3,4],
    capacity = 5,
    n = values.length;

console.log(JSON.stringify(knapSack(weights,values,capacity,n)));

结果:

D:\program\nodejs\node.exe .\src\test_math.js
1 5 选择物品1,重量:3,价值:4
src/test_math.js:45
0 2 选择物品0,重量:2,价值:3
src/test_math.js:54
[[0,0,3,3,3,3],[0,0,3,4,4,7],[0,0,3,4,5,7]]

改进方便理解:

function doDynamic(values,weights,capacity){
    let row = values.length;
    let arr = [];

    for (let i = 0; i < row; i++){
        arr[i] = [];
        for (let j = 0; j <= capacity; j++){
            if (j === 0){
                arr[i][j] = 0;
            }else{
                if (j < weights[i]){
                    if (i === 0){
                        arr[i][j] = 0;
                    }else {
                        arr[i][j] = arr[i-1][j]; 
                    }
                }else{
                    if (i === 0){
                        arr[i][j] = values[i];
                    }else{
                        arr[i][j] = Math.max(values[i] + arr[i-1][j-weights[i]],arr[i-1][j] );
                    }
                }
            }
        }
    }
    return arr;
}


let values = [3,4,5];
let weights = [2,3,4];
let capacity = 5;
let arr = doDynamic(values,weights,capacity);
console.log('v','w',JSON.stringify([0,1,2,3,4,5]));
arr.forEach((v,i)=>{
    console.log(values[i],weights[i],JSON.stringify(v));
})

结果:

v w [0,1,2,3,4,5]
3 2 [0,0,3,3,3,3]
4 3 [0,0,3,4,4,7]
5 4 [0,0,3,4,5,7]

参考:https://juejin.cn/post/6844903607855251463

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176910.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

车辆占用应急车道识别抓拍系统 opencv

车辆占用应急车道识别抓拍系统通过opencvpython人工智能识别技术&#xff0c;对高速公路应急车道进行不间断实时监测&#xff0c;当监测到应急车道上有车辆违规占用时&#xff0c;立即告警提醒后台人员及时处理避。OpenCV的全称是Open Source Computer Vision Library&#xff…

【18】C语言 | 数组详解

目录 1、数组的格式 2、下列有什么区别 3、维数组的使用 4、*p 和 int* p arr 的含义 5、二维数组&#xff1a;打印一个二维数组 6、二维数组在数组中的存储 7、数组作为函数参数 8、数组名是数组首元素的地址 1、数组的格式 数组是一组相同类型元素的集合。 数组的创…

20230123英语学习

Interesting Studies to Spark Your Interest in the Research Field 科研也可以很有趣&#xff01;盘点那些好玩的研究 When it comes to picking studies worth reading, what scientists deem an interesting science article might be perceived differently by a person…

【Datewhale一起吃瓜 Task2】啃瓜第三章

文章目录线性模型关键&#xff1a;找到合适的w和b如何找到合适的 w和b&#xff1f;偏导为什么可以&#xff1f;推广线性模型 任务&#xff1a;找出一条线能够对数据进行划分或预测趋势 关键&#xff1a;找到合适的w和b 更适合于连续性的数值&#xff0c;如果数据是离散的如色…

AcWing 1020. 潜水员(二维费用背包)

一、问题 二、思路 这道题其实很容易看出是一个二维费用背包的变形&#xff0c;如果我们将氧气看作体积&#xff0c;将氮气看作价值的话&#xff0c;这道题就变成了从iii个物品里面选&#xff0c;体积至少为mmm&#xff0c;价值至少为nnn的条件下&#xff0c;所携带的物品的最…

Maplab:一个用于视觉惯性建图和定位研究的开源框架

摘要 鲁棒且精确的视觉惯性估计是当今机器人领域的重要挑战。能够用先验地图(prior map)进行定位(localize)并获得准确且无漂移的姿态估计&#xff0c;可以推动该系统的适应性。然而&#xff0c;目前大多数可用的解决方案都集中在单次使用&#xff0c;缺乏定位能力或端到端流水…

Java基本类型和包装类什么情况下判断相等(“==“或“equals“)?

[1] 先讨论一个面试题 int a 1; Integer b 1; Integer c new Integer(1); Integer d Integer.valueOf(1); int e d; int f d.intValue();请问以下式子的值&#xff1f;为什么&#xff1f; a b // true a c // true b c // false[2] ""与"equals"…

C++设计新思维(泛型编程与设计模式之应用)之常整数映射为类别(2.4)

技术 模板偏特化&#xff0c;模板全特化 应用 1、有必要根据一个编译期常数调用一个或数个不同的函数 2、有必要在编译器实施"分派"(dispatch) 例子 如果打算在执行期进行分派(dispatch)&#xff0c;可使用if-else或switch语句。大部分时候其执行期成本都微不足…

windows权限维持方法详解

权限维持在获取服务器权限后&#xff0c;为了防止服务器管理员发现和修补漏洞而导致对服务器权限的丢失&#xff0c;测试人员往往需要采取一些手段来实现对目标服务器的持久化访问。权限持久化&#xff08;权限维持&#xff09;技术就是包括任何可以被测试人员用来在系统重启、…

Allegro如何通过飞线判断同一个网络连接位是否在同一直线操作指导

Allegro如何通过飞线判断同一个网络连接位是否在同一直线操作指导 Allegro可以通过飞线判断同一个网络的连接位是否在同一条直线上,如下图 当飞线是类似三角形的时候,可以判定两个连接点位是在同一条直线上 具体设置操作如下 选择Setup选择Design Parameter

React源码之render过程中发生了什么?

理解JSX 对于我们直接书写jsx语法&#xff0c;我们的浏览器是不理解我们这种语法的&#xff0c;所以需要babel来去转义&#xff0c;那么可以通过plugin-transform-react-jsx来转译jsx语法&#xff0c;使得浏览器可以识别我们的Jsx语法&#xff0c;例如&#xff1a; <div&g…

关于xshell简答使用

xshell是一个远程工具下载 官网地址&#xff1a;https://www.xshell.com/zh/xshell/直接下载即可~选择免费的授权页面 下载 不然要收费 也不要用盗版。运行后的xshell界面我们要建立服务器的连接点击加号 新建连接 即可 输入连接地址后 要输入 账号 和 秘密 OK 好了 可以使用了…

【Linux】Linux编译器gcc、g++

文章目录&#x1f3aa; Linux编译器gcc、g&#x1f680;1. 程序的编译⭐1.1 预处理⭐1.2 编译⭐1.3 汇编⭐1.4 链接⭐1.5 gcc/g常用指令&#x1f680;2. 函数库⭐2.1 静态库⭐2.2 动态库⭐2.3 动静态库对比&#x1f3aa; Linux编译器gcc、g 我们这以gcc为例&#xff0c;g编译器…

【学习笔记之数据结构】树的认识

树的概念&#xff1a; 树是一种非线性的数据结构&#xff0c;它由n&#xff08;n可以为0&#xff09;个有限的节点组成一个具有层次关系的结合。之所以把它称之为树是因为它的逻辑结构形似一个倒着的树。它的根在上面&#xff0c;叶子在下面。   有一个特殊的节点&#xff0c…

WebRTC系列-Qos系列之接收NACK

文章目录 1. 主要调用流程1.1 RTCP和RTP包区分1.2 查找丢失包2. RTX在文章 WebRTC系列-Qos系列之RTP/RTCP源码分析-RTP/RTCP包解析-3的2.3.3章节介绍了NACK包在WebRTC中解析方式及RFC规定协议的定义。 这篇文章开始,详细分析其接受RTCP包到解析nack的处理流程; 1. 主要调用流…

Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解

博主前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住也分享一下给大家 &#x1f449;点击跳转到教程 一、Kotlin的可空性 null 在java中我们司空见惯的空指针异常NullPointerException,带给了我们很多麻烦。 Kotlin作为更强…

STM32编写OLED显示屏驱动

文章目录前言一、OLED的器件地址二、编写写数据和写命令函数三、编写初始化OLED屏幕函数四、其他功能函数编写五、显示字符和数字函数编写总结前言 这篇文章将带大家学习如何编写OLED显示屏的驱动程序。这里我使用的是HAL库的硬件IIC&#xff0c;OLED屏幕使用的是SSD1306的。 …

sbt编程语言scala的构建工具配置及项目构建(附带网盘下载)

SBT简介 SBT 是 Scala 的构建工具&#xff0c;全称是 Simple Build Tool&#xff0c; 类似 Maven 或 Gradle。 Java可以用Maven快速构建项目&#xff0c;scala用SBT快速构建一个Scala项目。 sbt下载官网 百度网盘链接&#xff1a;https://pan.baidu.com/s/1eJkdWndZ0izcd3w…

Elasticsearch7.8.0版本高级查询——桶聚合查询文档

目录一、初始化文档数据二、桶聚合查询文档2.1、概述2.2、terms 聚合&#xff0c;分组统计的示例2.3、在 terms 分组下再进行聚合的示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求…

Yolo系列理论

参考文章&#xff1a;AI菌的YOLO系列 目标检测-Yolo系列发展Anchors Base原理Anchors Free原理YOLO v1Yolov1网络结构Yolov1实现方法Yolov1损失函数Yolov1总结YOLO v2Yolov2网络结构Yolov2改进细节Yolov2总结YOLO V3Yolov3网络结构YOLO V4Yolov4网络结构YOLOv4 BackBone训练策略…