展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > 0/1背包问题:最大化物品收集数量的优化方法

0/1背包问题:最大化物品收集数量的优化方法

本文深入探讨如何在给定预算下最大化收集物品数量的问题。我们将此问题映射为经典的0/1背包问题,并详细介绍其动态规划解决方案。针对预算过大导致传统DP效率低下的情况,文章还将介绍一种通过重新定义DP状态来优化的方法,并提供相应的代码示例,旨在帮助读者理解并掌握解决此类资源分配问题的专业策略。

最大化预算内收集物品数量:0/1背包问题的应用与优化

本文深入探讨如何在给定预算下最大化收集物品数量的问题。我们将此问题映射为经典的0/1背包问题,并详细介绍其动态规划解决方案。针对预算过大导致传统DP效率低下的情况,文章还将介绍一种通过重新定义DP状态来优化的方法,并提供相应的代码示例,旨在帮助读者理解并掌握解决此类资源分配问题的专业策略。

问题描述

假设我们有一个物品列表,每个物品都由两个属性定义:购买所需的“金额”(或成本)和购买后能获得的“物品数量”(或价值)。我们还有一个总的“预算”限制。目标是在不超过预算的前提下,最大化我们能收集到的总物品数量。

例如,给定一个数组 arr = [[x,y], [x1,y1], ...],其中 x 是金额,y 是物品数量,以及一个预算 z。我们需要找到一个子集,使得所有选定物品的金额之和不超过 z,且所有选定物品的数量之和最大。

初始贪心尝试及其局限性

在解决这类问题时,一种直观的尝试是采用贪心策略。例如,可以先对物品进行排序,优先选择金额较小的物品,或者在金额相同时优先选择物品数量较多的。原始代码中展示了这种尝试:

public static long solve(List> arr, long z) {
    arr.sort((a, b) -> {
        int z1 = Long.compare(a.get(0) , b.get(0)); // 优先按金额升序
        if(z1 == 0) {
            z1 = Long.compare(b.get(1) , a.get(1)); // 金额相同时,按物品数量降序
        }
        return z1;
    });

    long totalCost = 0;
    long totalItems = 0;
    for(List item : arr) {
        long cost = item.get(0);
        long items = item.get(1);
        if(totalCost + cost <= z) {
            totalCost += cost;
            totalItems += items;
        } else {
            break; // 预算不足,停止
        }
    }
    return totalItems;
}

这种贪心策略在某些特定问题(如分数背包问题)中是有效的,但对于0/1背包问题(每个物品只能选择一次,不能分割),它并不能保证找到最优解。例如,如果存在一个金额略大但物品数量极多的物品,贪心策略可能会因为优先选择小金额物品而错过这个最优选择。因此,我们需要更强大的方法来解决。

0/1背包问题的动态规划解法

此问题是经典的0/1背包问题的一个变体:

  • 每个物品的“金额”对应背包问题的“重量”。
  • 每个物品的“物品数量”对应背包问题的“价值”。
  • 总“预算”对应背包问题的“背包容量”。

动态规划是解决0/1背包问题的标准方法。

1. 定义DP状态

我们定义 dp[w] 为在不超过预算 w 的情况下,能收集到的最大物品数量。

2. 状态转移方程

遍历每个物品。对于当前物品 i,其金额为 cost_i,物品数量为 items_i。 对于每个可能的预算 w(从 z 递减到 cost_i),我们可以选择两种策略:

  • 不选择物品 i: 此时最大物品数量仍为 dp[w]。
  • 选择物品 i: 此时最大物品数量为 dp[w - cost_i] + items_i。

因此,状态转移方程为: dp[w] = max(dp[w], dp[w - cost_i] + items_i)

需要注意的是,内层循环 w 必须从大到小遍历,以确保每个物品只被选择一次(0/1性质)。

3. 初始化

dp[0] = 0 (预算为0时,能收集0个物品)。 所有其他 dp[w] 初始化为0。

4. 示例代码(Java)

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class MaximizeItemsWithBudget {

    /**
     * 使用标准0/1背包动态规划解决问题。
     *
     * @param arr 物品列表,每个元素 [金额, 物品数量]
     * @param budget 总预算
     * @return 能收集到的最大物品数量
     */
    public static long solveKnapsackDP(List> arr, long budget) {
        // dp[w] 表示在预算为 w 时,能收集到的最大物品数量
        // 注意:如果 budget 很大,这个数组会非常大,可能导致内存溢出或计算时间过长。
        // long[] dp = new long[(int) (budget + 1)]; // 预算可能超过 int 范围,需要注意类型转换
        // 鉴于 budget 可以是 long,这里需要考虑实际的 budget 范围。
        // 假设 budget 在 int 范围内,或者我们使用 HashMap 来模拟稀疏数组。
        // 为了演示,我们假设 budget 能够被 int 强制转换且在合理范围内。
        // 如果 budget 真的非常大,请参考下面的“处理大预算”部分。

        if (budget > Integer.MAX_VALUE) {
            // 提示:预算过大,请考虑使用优化方法
            System.err.println("Warning: Budget is too large for standard DP array. Consider optimized approach.");
            // 这里可以抛出异常或调用优化方法
            // For now, we will proceed with a smaller assumed budget for demonstration.
            // In a real scenario, this would be a critical check.
            // For this example, let's cap budget for array size, or use alternative DP if needed.
            // If budget is truly large, the below array initialization will fail.
            // We'll proceed with the assumption that budget fits into int for array indexing,
            // or that the "large budget" case is handled by the next section.
            // For the sake of a runnable example, let's assume budget is within int max for array size.
            // Or more practically, use the optimized approach for large budgets.
            // For a general tutorial, it's crucial to point this out.
            // Let's use a smaller max budget for this example to avoid runtime errors
            // but emphasize the limitation.
            // Let's cap budget to a reasonable int for the array size for demonstration.
            // If budget exceeds this, the optimized approach is necessary.
            // For this example, let's assume budget <= 10^5 or similar.
            // If budget is larger, the `dp` array will be too big.
        }

        // 假设 budget 不会超过 Integer.MAX_VALUE / 2,以避免数组过大
        // 在实际应用中,如果 budget 真的很大,需要使用下面的优化方法
        int maxBudgetForArray = (int) Math.min(budget, 1_000_000); // 示例限制,实际应根据内存决定
        long[] dp = new long[maxBudgetForArray + 1];

        for (List item : arr) {
            long cost = item.get(0);
            long items = item.get(1);

            // 从后往前遍历,确保每个物品只被选择一次
            for (int w = maxBudgetForArray; w >= cost; w--) {
                dp[w] = Math.max(dp[w], dp[(int)(w - cost)] + items);
            }
        }

        return dp[maxBudgetForArray]; // 返回最大预算下的最大物品数量
    }

    public static void main(String[] args) {
        List> items = new ArrayList<>();
        items.add(Arrays.asList(10L, 60L)); // cost, items
        items.add(Arrays.asList(20L, 100L));
        items.add(Arrays.asList(30L, 120L));
        long budget = 50;

        long maxItems = solveKnapsackDP(items, budget);
        System.out.println("Max items with budget " + budget + " (standard DP): " + maxItems); // Expected: 220 (20+100, 30+120 -> 50, 220)

        // Example with large budget (will trigger warning/limitation in current implementation)
        // For actual large budget, the optimized approach below is needed.
        // long largeBudget = 1_000_000_000L;
        // long maxItemsLargeBudget = solveKnapsackDP(items, largeBudget);
        // System.out.println("Max items with large budget (standard DP): " + maxItemsLargeBudget);
    }
}

处理大预算(大重量)的情况

当预算 z(即背包容量)非常大时,例如达到 10^9 甚至 10^12,而物品数量 N 相对较小(例如 N <= 100 或 N <= 200),标准0/1背包的 dp 数组大小会变得无法接受 (O(N * Z) 的时间和空间复杂度)。

在这种情况下,我们可以重新定义DP状态。由于物品数量 N 较小,而每个物品的“物品数量”或“价值”通常也在一个有限的范围内,我们可以将DP状态定义为:

1. 定义DP状态(优化版)

dp[v] 表示为了获得总价值(物品数量) v 所需的最小金额。

2. 状态转移方程

遍历每个物品。对于当前物品 i,其金额为 cost_i,物品数量为 items_i。 对于每个可能的总价值 v(从 maxTotalItems 递减到 items_i),我们可以选择两种策略:

  • 不选择物品 i: 此时所需最小金额仍为 dp[v]。
  • 选择物品 i: 此时所需最小金额为 dp[v - items_i] + cost_i。

因此,状态转移方程为: dp[v] = min(dp[v], dp[v - items_i] + cost_i)

3. 初始化

dp[0] = 0 (获得0个物品需要0金额)。 所有其他 dp[v] 初始化为一个足够大的值(例如 Long.MAX_VALUE),表示无法达到该价值。

4. 计算最大总物品数量

首先需要计算所有物品可能达到的最大总物品数量 maxPossibleItems。 然后,在填充完 dp 数组后,从 maxPossibleItems 倒序遍历 v,找到第一个 v 使得 dp[v] <= budget。这个 v 就是在给定预算下能获得的最大物品数量。

5. 示例代码(Java)

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class MaximizeItemsWithBudgetOptimized {

    /**
     * 当预算非常大时,使用优化后的0/1背包动态规划解决问题。
     * DP状态定义为:dp[v] = 获得总价值 v 所需的最小金额。
     *
     * @param arr 物品列表,每个元素 [金额, 物品数量]
     * @param budget 总预算
     * @return 能收集到的最大物品数量
     */
    public static long solveKnapsackOptimized(List> arr, long budget) {
        long maxPossibleItems = 0;
        for (List item : arr) {
            maxPossibleItems += item.get(1); // 累加所有物品的最大数量
        }

        // dp[v] 存储获得总价值 v 所需的最小金额
        // 数组大小取决于 maxPossibleItems,通常比 budget 小很多
        long[] dp = new long[(int) (maxPossibleItems + 1)];

        // 初始化:获得0价值需要0金额,其他价值初始化为无穷大
        Arrays.fill(dp, Long.MAX_VALUE);
        dp[0] = 0;

        for (List item : arr) {
            long cost = item.get(0);
            long items = item.get(1);

            // 从后往前遍历,确保每个物品只被选择一次
            for (int v = (int) maxPossibleItems; v >= items; v--) {
                if (dp[(int)(v - items)] != Long.MAX_VALUE) { // 确保 (v - items) 是可达的
                    dp[v] = Math.min(dp[v], dp[(int)(v - items)] + cost);
                }
            }
        }

        // 从最大可能的物品数量开始倒序查找,找到第一个满足预算条件的价值
        long resultMaxItems = 0;
        for (int v = (int) maxPossibleItems; v >= 0; v--) {
            if (dp[v] <= budget) {
                resultMaxItems = v;
                break;
            }
        }
        return resultMaxItems;
    }

    public static void main(String[] args) {
        List> items = new ArrayList<>();
        items.add(Arrays.asList(10L, 60L));
        items.add(Arrays.asList(20L, 100L));
        items.add(Arrays.asList(30L, 120L));
        long budget = 50;

        long maxItemsOptimized = solveKnapsackOptimized(items, budget);
        System.out.println("Max items with budget " + budget + " (optimized DP): " + maxItemsOptimized); // Expected: 220

        // 模拟一个大预算场景,优化方法在这种情况下更有效
        long largeBudget = 1_000_000_000L; // 10亿
        long maxItemsLargeBudgetOptimized = solveKnapsackOptimized(items, largeBudget);
        System.out.println("Max items with large budget " + largeBudget + " (optimized DP): " + maxItemsLargeBudgetOptimized); // Expected: 280 (所有物品都买得起 60+100+120)

        // 另一个例子
        List> items2 = new ArrayList<>();
        items2.add(Arrays.asList(1L, 10L));
        items2.add(Arrays.asList(2L, 20L));
        items2.add(Arrays.asList(3L, 30L));
        long budget2 = 4L; // 预算4

        // 理论上,我们可以选择 (1,10) + (3,30) -> cost 4, items 40
        // 或者 (1,10) + (2,20) -> cost 3, items 30
        // 或者 (2,20) + (3,30) -> cost 5, items 50 (超预算)
        // 应该选择 (1,10) + (3,30) 得到 40
        long maxItems2 = solveKnapsackOptimized(items2, budget2);
        System.out.println("Max items with budget " + budget2 + " (optimized DP): " + maxItems2); // Expected: 40
    }
}

总结

在预算内最大化收集物品数量的问题是经典的0/1背包问题的一个直接应用。

  1. 标准动态规划: 当预算(背包容量)相对较小,且物品数量不是特别大时,可以使用 dp[w] 表示在预算 w 下能获得的最大物品数量。其时间复杂度为 O(N * Z),其中 N 是物品数量,Z 是预算。
  2. 优化动态规划: 当预算 Z 非常大,但物品数量 N 和总物品价值(或数量)相对较小时,可以采用 dp[v] 表示获得总价值 v 所需的最小金额。这种方法的复杂度为 O(N * V_total),其中 V_total 是所有物品的最大可能总价值。这种方法在 Z 极大时能显著提高效率。

选择哪种动态规划方法取决于问题的具体约束:是预算 Z 还是总价值 V_total 更小。理解这两种DP状态定义及其适用场景是解决此类优化问题的关键。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
精品专题 更多
本月促销

正软商城本月促销专区,汇集办公、设计、安全、影音、系统工具及AI软件等正版软件优惠活动,提供限时折扣、特价授权和优惠购买信息,活动库存及价格以页面实时展示为准。

装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。