算法学习笔记(8.3)-(0-1背包问题)

news2025/1/7 6:04:27

目录

最常见的0-1背包问题:

第一步:思考每轮的决策,定义状态,从而得到dp表

第二步:找出最优子结构,进而推导出状态转移方程

第三步:确定边界条件和状态转移顺序

方法一:暴力搜素

代码示例:

方法二:记忆化搜索

时间复杂度取决于子问题数量,也就是O(n*cap)。

实现代码如下:

方法三:动态规划

代码如下所示:

方法四:空间优化

代码示例

最常见的0-1背包问题:

Question:给定n个物品,第i个物品的重量为wgt[i]、价值为val[i],和一个容量为cap的背包。每个物品只能选择一次,问在限定背包的容量下能放入物品的最大价值。

观察图下所示,由于物品编号i从1开始计数,数组索引从0开始计数,因此物品i对应重量wgt[i]和价值val[i]。

我们可以将0-1背包问题看作一个由n轮决策组成的过程,对于每个物体都有不放入和放入两种决策,因此该问题满足决策树模型。

该问题的目标是求解“在限定背包容量下能放入物品的最大价值”,因此较大概率是一个动态规划的问题。

第一步:思考每轮的决策,定义状态,从而得到dp表

对于每个物品来说,不放入背包,背包容量不变;放入背包,背包容量减小。由此可得状态定义:当前物品编号i和剩余背包容量c,记作[i,c]。

状态[i,c]对应的子问题为:前i个物品在剩余容量为c的背包中的最大价值,记为dp[i,c]。

待求解的是dp[n,cap],因此需要一个尺寸为(n+1) * (cap+1)的二维dp表。

第二步:找出最优子结构,进而推导出状态转移方程

当我们做出物品i的决策后,剩余的是前i-1个物品的决策,可分为以下两种情况。

  1. 不放入物品i:背包容量不变,状态变化为[i-1,c].
  2. 放入物品i:背包容量减少wgt[i-1],价值增加val[i-1],状态变化为[i-1,c-wgt[i-1]]。

上述分析揭示了本题的最优子结构:最大价值dp[I,c]等于不放入物品i和放入物品i两种方案中价值更大的那一个。由此可以推导出状态转移方程为:

dp[i,c] = max(dp[i-1,c],dp[i-1,c-wgt[i-1] ] + val[i-1])

需要注意是,当前物品重量wgt[i-1]超过剩余背包容量c,则只能选择不放入背包。

第三步:确定边界条件和状态转移顺序

当无物品时或无背包剩余容量时最大价值为0,即首列dp[i,0]和首列dp[0,c]都等于0.

当前状态[i,c]从上方的状态[i-1,c]和左上方的状态[i-1,c-wgt[i-1]]转移而来,因此通过两层循环正序遍历整个dp表即可。

根据以上的分析,采取三种方法顺序进行实现暴力搜索,记忆化搜索,动态规划解法。

方法一:暴力搜素

搜索代码需要包含的要素:

  1. 递归参数:状态[i,c]
  2. 返回值:子问题的解dp[i,c]
  3. 终止条件:当物品编号越界I = 0 或背包容量为0时,终止递归并返回价值0.
  4. 剪枝:当前物品重量超出背包剩余容量时,则只能选择不放入背包。
代码示例:
# python 代码示例

def knapsack_dfs(wgt, val, i, c) :
    if i == 0 or c == 0 :
        return 0
    if wgt[i - 1] > c :
        return knapsack(wgt, val, i - 1, c)
    nohave = knapsack_dfs(wgt, val, i - 1, c)
    have = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
    return max(nohave, have)
// C++代码示例

int knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) :
    if (i == 0 || c == 0)
    {
        return 0 ;
    }
    if (wgt[i - 1] > c)
    {
        return knapsackDFS(wgt, val, i - 1, c) ;
    }
    int nohave = knapsackDFS(wgt, val, i - 1, c) ;
    int have = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] ;
    return max(nohave, have) ;

如图所示:由于每个物品都会产生选或者不选两条搜索分支,因此时间复杂度为O(2^n)。

观察递归树,容易发现其中存在重叠子问题,例如dp[1,10]。而当物品较多,背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅度增多。

方法二:记忆化搜索

为了保证重叠子问题只被计算一次,我们借助记忆列表mem来记录子问题的解,其中menm[i][c]对应dp[i][c]。

时间复杂度取决于子问题数量,也就是O(n*cap)。
实现代码如下:
# python 代码示例
def knapsack_dfs_mem(wgt, val, i, c, mem) :
    if i == 0 or c == 0 :
        return 0
    if mem[i][c] != -1 :
        return mem[i][c]
    if wgt[i - 1] > c :
        return knapsack_dfs_mem(wgt, val, i - 1, c, mem)
    noput = knapsack_dfs_mem(wgt, val, i - 1, c, mem)
    yesput = knapsack_dfs_mem(wgt, val, i - 1, c - wgt[i - 1], mem) + val[i - 1]
    mem[i][c] = max(noput, yesput)
    return mem[i][c]
// c++ 代码示例
int knapSackDFSMem(vector<int> &wgt, vector<int> &val, int i, int c, vector<int> &mem)
{
    if (i == 0 || c == 0)
    {
        return 0 ;
    }
    if (mem[i][c] != 0)
    {
        return mem[i][c] ;
    }
    if (wgt[i - 1] > c)
    {
        return knapSackDFSMem(wgt, val, i - 1, c, mem) ;
    }
    int noput = knapSackDFSMem(wgt, val, i - 1, c, mem) ;
    int yesput = knapSackDFSMem(wgt, val, i - 1, c - wgt[i - 1], mem) + val[i - 1];
    mem[i][c] = max(noput, yesput) ;
    return mem[i][c] ;
}

方法三:动态规划

动态规划实质是就是在状态转移的过程中填充dp表的过程,

代码如下所示:
# python 代码示例
def knapsack_dp(wgt, val, cap) :
    n = len(wgt)
    dp = [ [0] * (cap + 1) for _ in range(n + 1)]
    for i in range(1, n + 1) :
        for c in range(1, cap + 1) :
            if wgt[i - 1] > c :
                dp[i][c] = dp[i - 1][c]
            else :
                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) 
    return dp[n][cap] 
// c++ 代码示例
int knapSackDP(vector<int> &wgt, vector<int> &val, int cap)
{ 
    int n = wgt.size() ;
    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0)) ;
    for (int i = 1 ; i <= n ; i++)
    {
        for (int c = 1 ; c <= cap ; c++)
        {
            if (wgt[i - 1] > c)
            {
                dp[i][c] = dp[i - 1][c] ;
            }
            else
            {
                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])  ;
            }
        }
    }
    return dp[n][cap] ;
}

时间复杂度和空间复杂度都是由数组dp所决定的,即O(n*cap)。

如图所示:

方法四:空间优化

由于每个状态都至于其上一行有关系,因此我们可以使用两个数组进行滚动前进,将复杂度从O(n^2)降低为O(n)。

进一步思考,我们能否仅用一个数组实现空间优化?观察可知,每个状态都是有正上方或左上方的格子的状态转移而来。假设只有一个数组,当开始遍历第i行时,该数组存储仍然是第i-1行的状态。

  1. 如果采取正序遍历,那么遍历到dp[i,j]时,左上方的dp[i-1,1]~dp[i-1,j-1]值可能已经覆盖了,因此无法得到状态转移的正确结果。
  2. 如果采取倒序遍历,则不会发生覆盖问题,状态转移可以正确的进行。

代码示例:
def knap_sack_dp_comp(wgt, val, cap) :
    n = len(wgt)
    dp = [0] * (cap + 1)
    for i in range(1, n + 1) :
        for c in range(cap, 0 ,-1) :
            if wgt[i - 1] > c :
                dp[c] = dp[c]
            else :
                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
    return dp[cap]    
// c++ 代码示例
int kanpSackDPComp(vector<int> &wgt, vector<int> &val, int cap)
{
    int n = wgt.size() ;
    vector<int> dp(cap + 1, 0) ;
    for (int i = 1 ; i <= n ; i++)
    {
        for (int c = cap; c > 0 ; c--)
        {
            if (wgt[i - 1] > c)
            {
                dp[c] = dp[c] ;
            }
            else
            {
                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) ;
            }
        }
    }
    return dp[cap] ;
}

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

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

相关文章

经典元启发式算法的适用范围及基本思想

元启发式算法是针对优化问题设计的一类高级算法&#xff0c;它们具有广泛的适用性&#xff0c;可以解决不同类型的问题。不同的元启发式算法由于其特定的搜索机制和策略&#xff0c;适用的优化问题类型也有所不同。以下是一些常见元启发式算法及其适用范围&#xff1a; 1. 遗传…

OrangePi AIpro 浅上手及搭建卡通图像生成多元化AI服务

前言 很高兴&#xff0c;收到了一份新款 OrangePi AIpro 开发板&#xff0c;这是香橙派第一次与华为昇腾合作&#xff0c;使用昇腾系列 AI 处理器来设计这款高性价比的 AI 开发板。这块开发板不仅性能强大&#xff0c;还支持丰富的硬件接口&#xff0c;为AI开发者提供了一个理…

Nginx的访问限制与访问控制

访问限制 访问限制是一种防止恶意访问的常用手段&#xff0c;可以指定同一IP地址在固定时间内的访问次数&#xff0c;或者指定同一IP地址在固定时间内建立连接的次数&#xff0c;若超过网站指定的次数访问将不成功。 请求频率限制配置 请求频率限制是限制客户端固定时间内发…

代码随想录第十二天|226.翻转二叉树、 101.对称二叉树、104.二叉树的最大深度、111.二叉树的最小深度

文章目录 226.翻转二叉树思路解法一、前序遍历递归解法二、深度优先搜索--迭代 101.对称二叉树思路解法一、递归解法二、迭代 104.二叉树的最大深度解法一、深度优先搜索--递归解法二、广度优先搜索--迭代 111.二叉树的最小深度解法一、深度优先搜索--递归解法二、广度优先搜索…

制作显卡版docker并配置TensorTR环境

感谢阅读 相关概念docker准备下载一个自己电脑cuda匹配的docker镜像拉取以及启动镜像安装cudaTensorRT部署教程 相关概念 TensorRT是可以在NVIDIA各种GPU硬件平台下运行的一个模型推理框架&#xff0c;支持C和Python推理。即我们利用Pytorch&#xff0c;Tensorflow或者其它框架…

2、matlab打开显示保存点云文件(.ply/.pcd)以及经典点云模型数据

1、点云数据简介 点云数据是三维空间中由大量二维点坐标组成的数据集合。每个点代表空间中的一个坐标点&#xff0c;可以包含有关该点的颜色、法向量、强度值等额外信息。点云数据可以通过激光扫描、结构光扫描、摄像机捕捉等方式获取&#xff0c;广泛应用于计算机视觉、机器人…

常用控件(六)

布局管理器 布局管理器垂直布局QHBoxLayoutQGridLayoutQFormLayoutQSpacerItem 布局管理器 之前使⽤ Qt 在界⾯上创建的控件, 都是通过 “绝对定位” 的⽅式来设定的. 也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move ⽅式摆放过去.这种设定⽅式其…

JAVA NIO组件之Buffer详解

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

标签-镜像拉取策略-重启策略-pod优雅终止-pod中容器资源限制-容器类型-容器守护进程-日志排错-容器命令exec-cp

一.标签 1.概述&#xff1a; 标签是附加到kubernets对象&#xff08;比如pod&#xff09;上的键值对&#xff0c;标签可以在创建时附加到对象&#xff0c;随后也可以随时添加修改&#xff1b;标签不支持唯一性。 在k8s中大多数资源都是通过标签进行关联的&#xff08;如pod与s…

管理Linux本地用户和组

什么是用户 用户账户在可以运行命令的不同人员和程序之间提供安全界限。 在Linux系统中&#xff0c;系统通过分配唯一的标识号&#xff08;用户ID或UID&#xff09;来区分不同的用户帐户。 在Linux系统中&#xff0c;用户帐户有以下三种主要类型&#xff1a; 超级用户 负责…

跨平台APP开发工具的选择

跨平台APP开发工具允许开发者编写一次代码,然后在多个平台上运行,如iOS、Android等,这极大地提高了开发效率并降低了维护成本。下面是一些流行的跨平台APP开发工具及其特点: React Native 开发公司:Facebook编程语言:JavaScript特点: 使用React的组件模型构建原生应用。…

无损音乐播放器推荐:Audirvana for Mac 中文激活版

udirvana 是一款高品质的音乐播放软件&#xff0c;专为Mac操作系统设计。它被设计来提供音频播放的最高标准&#xff0c;支持多种音频格式&#xff0c;包括高达32位/192kHz的高分辨率音频。Audirvana Plus 是其高级版本&#xff0c;提供了更多的功能和优化&#xff0c;例如音频…

stm32h743 NetXduo 实现http server CubeIDE+CubeMX

在这边要设置mpu的大小,要用到http server,mpu得设置的大一些 我是这么设置的,做一个参考 同样,在FLASH.ld里面也要对应修改,SECTIONS里增加.tcp_sec和 .nx_data两个区,我们用ram_d2区域去做网络,这个就是对应每个数据在d2区域的起点。 在CubeMX里,需要用到filex、dhc…

全栈智能家居系统设计方案:STM32+Linux+多协议(MQTT、Zigbee、Z-Wave)通信+云平台集成

1. 项目概述 随着物联网技术的快速发展,智能家居系统正在成为现代生活中不可或缺的一部分。本文介绍了一个基于STM32微控制器和Linux系统的智能家居解决方案,涵盖了硬件设计、软件架构、通信协议以及云平台集成等方面。 该系统具有以下特点: 采用STM32作为终端设备的控制核心…

【python学习】python标准库之正则表达式库re的定义、功能和函数,以及正则表达式的元字符和含义

引言 re 库是Python标准库的一部分&#xff0c;不需要额外安装。要使用 re 库&#xff0c;只需在代码中导入它 文章目录 引言一、re库的定义二、re 库中常用的功能和函数&#xff1a;2.1 搜索模式2.2 查找模式2.3 替换模式2.4 分割模式2.5 编译模式2.6 错误信息2.7 支持的模式2…

基础部分-变量

目录 1、一个程序就是一个世界&#xff0c;变量是程序的基本组成单位。 2、变量有三个基本要素&#xff1a;类型名称值 3、变量相当于内存中一个数据存储空间的表示 4、变量的使用步骤 &#xff08;先定义后使用&#xff09; 5、变量快速入门 6、格式化输出 7、程序中 号…

链接追踪系列-07.logstash安装json_lines插件

进入docker中的logstash 容器内&#xff1a; jelexbogon ~ % docker exec -it 7ee8960c99a31e607f346b2802419b8b819cc860863bc283cb7483bc03ba1420 /bin/sh $ pwd /usr/share/logstash $ ls bin CONTRIBUTORS Gemfile jdk logstash-core modules tools x-pack …

【Linux】进程控制的详细介绍

前言 在此之前&#xff0c;我们学过进程的概念&#xff0c;进程的状态&#xff0c;进程地址空间等一系列进程相关的问题。本章我们继续学习进程&#xff0c;我们要来学习一下进程的控制&#xff0c;关于进程等待&#xff0c;等问题。 目录 1.再次认识Fork函数1.1 fork()之后操…

什么是 Modbus协议?

一、网络中的协议是指什么&#xff1f; 网络协议是网络通信中至关重要的一部分&#xff0c;它定义了网络中两个或多个设备之间通信的规则、过程和格式。这些规则确保了计算机网络设备能够使用一种通用语言来传输和接收数据&#xff0c;而不管它们的设计、硬件或基础设施如何。…

MySQL-ubuntu环境下安装配置mysql

文章目录 什么是数据库&#xff1f;一、ubuntu环境下安装mysql二、配置mysql配置文件1.先登上root账号2.配置文件的修改show engines \G; mysql和mysqld数据库的基础操作登录mysql创建数据库显示当前数据库使用数据库创建表插入students表数据打印students表数据select * from …