动态规划(二):0-1背包

简介: 题目有 件物品,每件占据的空间大小为 、价值为 ,对于容量空间为 的背包,问能够承载的最大价值是多少分析对于第 件物品,只有两种状态,放入背包,或不放入背包。

题目

N 件物品,每件占据的空间大小为 w_i、价值为 v_i(1\le i\le N),对于容量空间为 V 的背包,问能够承载的最大价值是多少

分析

对于第 i 件物品(1\le i\le N),只有两种状态,放入背包,或不放入背包。所以在空间大小为 j(0\le j\le V),能够承载的最大价值根据第 i 件物品放入或不放入而定,且只有这两种情况。

解答

F(i,j) 表示物品数量为 i,空间大小为 j 时的最大价值。则有:

  1. i==1,j\lt w_i 时有:F(i,j)=0,表示当前空间大小 j 无法容纳第一件物品,所以价值为 0

  2. i ==1,j\ge w_i 时有:F(i,j)=v_i,表示当前空间大小 j 可以容纳第一件物品,所以价值为 v_i

  3. 2\le i\le N,j\lt w_i 时有:F(i,j)=F(i-1,j),表示当前空间大小 j 无法容纳第 i 件物品,所以价值为 i-1 件物品时的价值 F(i-1,j)

  4. 2\le i\le N,j\ge w_i 时有:F(i,j)=max[F(i-1,j),F(i-1,j-w_i)+v_i],表示当前空间大小 j 可以容纳第 i 件物品,所以价值取决于第 i 件物品放入或不放入哪一种情况价值更大;

对于 i 件物品,空间大小为 j 时:

  • 不放入第 i 件物品时价值为 F(i-1,j),即将所有空间 j 施加于前 i-1 件物品上;
  • 放入第 i 件物品时,价值为 F(i-1,j-w_i)+v_i,即第 i 件物品的价值与 j-w_i 空间下前 i-1 件物品的价值之和。

对于 01 背包问题,有两种描述,背包能够承载的最大价值、背包刚好装满时承载的最大价值。第二种描述增加了“装满”的条件约束,两种情况很类似,下面分别对无约束和有约束的情况进行讨论。

无装满约束

二维数组形式
def backpack(weightArr, valueArr, V):
    N = len(weightArr)
    arr = [[] for i in range(N)]  # Two-dimensional array
    for i in range(N):
        for j in range(V + 1): 
            if j < weightArr[i]:  # the space can not afford the element's weight
                arr[i].append(0 if i == 0 else arr[i - 1][j])
            else:  # the space can afford the element's weight
                if i == 0:  
                    arr[0].append(valueArr[0])
                else:
                    arr[i].append(max(arr[i - 1][j], arr[i - 1][j - weightArr[i]] + valueArr[i]))
        arr[i] = arr[i]
    return arr[-1][-1]

代码中存在两层循环,以二维数组的形式记录中间数据,分别记录不同物品个数在各个空间大小下的最大价值。循环内部存在两种判断,分别用于判断空间大小 j 是否能够容纳第 i 件物品,和判断当前是否是第一件物品。

  • j \lt weightArr[i],即当前空间无法容纳第 i 件物品,则继续判断是否是第一件物品,若 i == 0,即当前是第一件物品,则 F(i,j)=0,表示当前空间大小无法容纳第一件物品,最大价值为 0;若 i \gt 0,即当前不是第一件物品,则 F(i,j)=F(i-1,j),表示当前空间大小无法容纳第 i 件物品,最大价值等同于没有第 i 件物品时候的最大价值;
  • j \ge weightArr[i],即当前空间可以容纳第 i 件物品,则继续判断是否是第一件物品,若 i == 0,即当前是第一件物品,则 F(i,j)=valueArr[0],表示当前空间可以容纳第一件物品,因为只有第一件物品,所以最大价值为 valueArr[0];若 i \gt 0,即当前不是第一件物品,则 F(i,j)=max[F(i-1,j),F(i-1,j-w_i)+v_i],比较第 i 件物品放入或不放入的价值,选择出最大价值。

代码中存在两层循环,时间复杂度为 O(NV),使用了二维数组作为存储空间,空间复杂度为 O(NV)

观察推导公式:F(i,j)=max[F(i-1,j),F(i-1,j-w_i)+v_i],可以发现 F(i,j) 的值只与 F(i-1,j)F(i-1,j-w_i) 有关,对应到二维数组中即为,arr[i][j] 的值只与 arr[i-1][j]arr[i-1][j-weightArr[i]] 有关。所以若已知二维数组第 i-1 行的数组 arr[i-1],则推导第 i 行数组数据时,若从右向左推导,或者称为 j 值下标由大到小推导,则第 i 行数据可以覆盖在第 i-1 行数组。所以可以将二维数组空间优化为一维数组空间。

一维数组形式
def backpack2(weightArr, valueArr, V):
    N = len(weightArr)
    arr = [0] * (V + 1)  # One-dimensional array
    for i in range(N):
        for j in range(V, -1, -1):
            if j >= weightArr[i]:
                arr[j] = max(arr[j], arr[j - weightArr[i]] + valueArr[i])
    return arr[-1]

代码中仍存在两层循环,第二层循环中变量值 j 由大到小变化,循环体中仍存在判断,不过因为只存在一维数组,且数组元素初始化值为 0,所以不需要再判断是否是第一件物品。

代码中存在两层循环,时间复杂度为 O(NV),使用了一维数组作为存储空间,空间复杂度为 O(V)

其实无论一维数组或者二维数组形式,第二层循环范围不一定非要是 0 ~ V,因为此处只讨论 01 背包,所以若题目中给出的 V 值很大,大到即便将 N 件物品全部放入背包中,仍存在较大容量空闲的话,这种情况可以修改第二层循环范围为 0 ~ \sum_{i=1}^{N}w_i

有装满约束

二维数组形式
def backpackComplete(weightArr, valueArr, V):
    N = len(weightArr)
    arr = [[] for i in range(N)]  # Two-dimensional array
    for i in range(N):  
        for j in range(V + 1): 
            if j < weightArr[i]:  # the space can not afford the element's weight
                arr[i].append(arr[i - 1][j] if i > 0 else None)
            else:  # the space can afford the element's weight
                if i == 0:  
                    arr[0].append(valueArr[0] if j == weightArr[i] else None)
                else:
                    if arr[i - 1][j] and arr[i - 1][j - weightArr[i]]:  # the two values both exist
                        arr[i].append(max(arr[i - 1][j], arr[i - 1][j - weightArr[i]] + valueArr[i]))
                    elif not arr[i - 1][j] and not arr[i - 1][j - weightArr[i]]:  # the two values both not exist
                        arr[i].append(valueArr[i] if j == weightArr[i] else None)
                    else:  # only one value exist
                        arr[i].append(arr[i - 1][j] if arr[i - 1][j] else arr[i - 1][j - weightArr[i]] + valueArr[i])
        arr[i] = arr[i]
    return arr[-1][-1]

有装满约束与无装满约束较为类似,同样的两层循环,同样的两种判断。不同之处在于,当空间大小 j 不能刚好等于物品占据空间之和时,此时的价值为 None。所以此处的代码相对于无装满约束的代码,最直观的差异就是,当空间大小 j \ge weightArr[i]i\gt 0 ,通过推导公式求最大价值时,对 arr[i-1][j]arr[i-1][j-weightArr[i]] 是否为 None 进行了判断。

一维数组形式
def backpackComplete2(weightArr, valueArr, V):
    N = len(weightArr)
    arr = [None] * (V + 1)  # One-dimensional array
    for i in range(N):
        for j in range(V, -1, -1):
            if j >= weightArr[i]:
                if arr[j] and arr[j - weightArr[i]]:  # the two values both exist
                    arr[j] = max(arr[j], arr[j - weightArr[i]] + valueArr[i])
                elif not arr[j] and not arr[j - weightArr[i]]:  # the two values both not exist
                    arr[j] = valueArr[i] if j == weightArr[i] else None
                else:  # only one value exist
                    arr[j] = arr[j] if arr[j] else arr[j - weightArr[i]] + valueArr[i]
    return arr[-1]

比较有、无装满约束的一维数组形式,可以发现与二维数组同样的差异,即空间大小不刚好满足物品占据空间之和时,价值为 None,且对推导公式中的元素值进行了是否为 None 判断。

有、无装满约束,算法的时间复杂度和空间复杂度不变。不过可以发现一个很明显的地方:有装满约束时,数组的最后一项元素值 arr[-1][-1]arr[-1] 不一定是数组中的最大元素,而数组中的最大元素值一定等于无装满约束时数组的最后一项元素值。

相关文章
|
1天前
|
云安全 人工智能 自然语言处理
|
6天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
316 116
|
8天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
613 53
Meta SAM3开源:让图像分割,听懂你的话
|
21天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
5天前
|
人工智能 Java API
Java 正式进入 Agentic AI 时代:Spring AI Alibaba 1.1 发布背后的技术演进
Spring AI Alibaba 1.1 正式发布,提供极简方式构建企业级AI智能体。基于ReactAgent核心,支持多智能体协作、上下文工程与生产级管控,助力开发者快速打造可靠、可扩展的智能应用。
|
4天前
|
弹性计算 人工智能 Cloud Native
阿里云无门槛和有门槛优惠券解析:学生券,满减券,补贴券等优惠券领取与使用介绍
为了回馈用户与助力更多用户节省上云成本,阿里云会经常推出各种优惠券相关的活动,包括无门槛优惠券和有门槛优惠券。本文将详细介绍阿里云无门槛优惠券的领取与使用方式,同时也会概述几种常见的有门槛优惠券,帮助用户更好地利用这些优惠,降低云服务的成本。
270 132
|
8天前
|
机器学习/深度学习 人工智能 自然语言处理
AgentEvolver:让智能体系统学会「自我进化」
AgentEvolver 是一个自进化智能体系统,通过自我任务生成、经验导航与反思归因三大机制,推动AI从“被动执行”迈向“主动学习”。它显著提升强化学习效率,在更少参数下实现更强性能,助力智能体持续自我迭代。开源地址:https://github.com/modelscope/AgentEvolver
423 29
|
15天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
728 224

热门文章

最新文章