贪心算法应用:多重背包启发式问题详解
贪心算法应用:多重背包启发式问题详解
多重背包问题是经典的组合优化问题,也是贪心算法的重要应用场景。本文将全面深入地探讨Java中如何利用贪心算法解决多重背包问题。
多重背包问题定义
**多重背包问题(Multiple Knapsack Problem)**是背包问题的变种,描述如下:
- 给定一个容量为W的背包
- 有n种物品,每种物品i有:
- 重量w_i
- 价值v_i
- 最大可用数量c_i(每种物品可以选择0到c_i个)
- 目标:在不超过背包容量的前提下,选择物品使总价值最大
与0-1背包(每种物品只能选0或1个)和完全背包(每种物品无限)不同,多重背包中物品有数量限制。
问题分析
数学表达
最大化:Σ(v_i * x_i) 对于i=1到n
约束条件:
- Σ(w_i * x_i) ≤ W 对于i=1到n
- 0 ≤ x_i ≤ c_i 且 x_i为整数
关键特性
- 组合爆炸:可能的组合数量为Π(c_i+1),直接枚举不可行
- NP难问题:没有已知的多项式时间解法
- 贪心可行:虽然不能保证最优解,但能得到近似解
贪心算法解决方案
1. 基本贪心策略
三种常见的贪心策略:
- 价值优先:按价值降序选择
- 重量优先:按重量升序选择
- 价值密度优先:按价值/重量(v_i/w_i)降序选择
实践证明,价值密度优先策略通常效果最好。
2. Java实现基础版
import java.util.Arrays;
import java.util.Comparator;class Item {int weight;int value;int count;public Item(int weight, int value, int count) {this.weight = weight;this.value = value;this.count = count;}
}public class MultipleKnapsack {public static int greedySolution(Item[] items, int capacity) {// 按价值密度排序Arrays.sort(items, new Comparator<Item>() {@Overridepublic int compare(Item a, Item b) {double densityA = (double)a.value / a.weight;double densityB = (double)b.value / b.weight;return Double.compare(densityB, densityA); // 降序}});int totalValue = 0;int remainingCapacity = capacity;for (Item item : items) {if (remainingCapacity <= 0) break;// 计算可以取多少个当前物品int maxTake = Math.min(item.count, remainingCapacity / item.weight);if (maxTake > 0) {totalValue += maxTake * item.value;remainingCapacity -= maxTake * item.weight;}}return totalValue;}public static void main(String[] args) {Item[] items = {new Item(2, 10, 3),new Item(3, 5, 2),new Item(5, 15, 2),new Item(7, 7, 3),new Item(1, 6, 4)};int capacity = 15;System.out.println("最大价值: " + greedySolution(items, capacity));}
}
3. 算法复杂度分析
- 排序:O(n log n)
- 贪心选择:O(n)
总时间复杂度:O(n log n)
空间复杂度:O(1)(不包括输入存储)
改进的贪心算法
基础贪心算法可能不是最优的,我们可以通过以下方法改进:
1. 贪心+动态规划混合
public static int hybridSolution(Item[] items, int capacity) {// 先按贪心算法得到一个解int greedyValue = greedySolution(items, capacity);// 对高价值物品尝试动态规划Arrays.sort(items, (a, b) -> b.value - a.value);int n = items.length;int[] dp = new int[capacity + 1];for (int i = 0; i < n; i++) {Item item = items[i];for (int k = 1; k <= item.count; k++) {for (int w = capacity; w >= k * item.weight; w--) {dp[w] = Math.max(dp[w], dp[w - k * item.weight] + k * item.value);}}}return Math.max(greedyValue, dp[capacity]);
}
2. 多阶段贪心选择
public static int multiPhaseGreedy(Item[] items, int capacity) {// 阶段1:价值密度优先int phase1 = greedySolution(items, capacity);// 阶段2:纯价值优先Arrays.sort(items, (a, b) -> b.value - a.value);int phase2 = greedySolution(items, capacity);// 阶段3:纯重量优先Arrays.sort(items, (a, b) -> a.weight - b.weight);int phase3 = greedySolution(items, capacity);return Math.max(phase1, Math.max(phase2, phase3));
}
完全解与贪心解对比
动态规划解法(最优解)
public static int dpSolution(Item[] items, int capacity) {int[] dp = new int[capacity + 1];for (Item item : items) {for (int w = capacity; w >= 0; w--) {for (int k = 1; k <= item.count; k++) {if (w >= k * item.weight) {dp[w] = Math.max(dp[w], dp[w - k * item.weight] + k * item.value);}}}}return dp[capacity];
}
对比分析
方法 | 时间复杂度 | 空间复杂度 | 解的质量 |
---|---|---|---|
纯贪心 | O(n log n) | O(1) | 近似 |
动态规划 | O(nWC_max) | O(W) | 最优 |
贪心+动态规划混合 | O(n log n + nWC_limited) | O(W) | 较好 |
其中C_max是最大物品数量,C_limited是限制的高价值物品数量
性能优化技巧
-
物品预处理:
- 移除重量>W的物品
- 合并相同物品
- 对价值密度相同的物品进行捆绑
-
搜索剪枝:
public static int greedyWithPruning(Item[] items, int capacity) {Arrays.sort(items, (a, b) -> Double.compare((double)b.value/b.weight, (double)a.value/a.weight));int[] best = {0};backtrack(items, 0, capacity, 0, best);return best[0]; }private static void backtrack(Item[] items, int index, int remaining, int currentValue, int[] best) {if (index == items.length || remaining == 0) {if (currentValue > best[0]) best[0] = currentValue;return;}Item item = items[index];int maxTake = Math.min(item.count, remaining / item.weight);// 从最多开始尝试,贪心顺序for (int k = maxTake; k >= 0 && (maxTake - k) <= 100; k--) {if (remaining - k * item.weight >= 0) {// 剪枝:如果剩余容量全部用下一个物品也无法超越当前最优double upperBound = currentValue + k * item.value;if (index + 1 < items.length) {upperBound += (remaining - k * item.weight) * ((double)items[index+1].value/items[index+1].weight);}if (upperBound <= best[0]) break;backtrack(items, index + 1, remaining - k * item.weight, currentValue + k * item.value, best);}} }
-
并行处理:
public static int parallelGreedy(Item[] items, int capacity) {// 多种贪心策略并行执行int[] results = new int[3];Thread t1 = new Thread(() -> {Arrays.sort(items, (a, b) -> Double.compare((double)b.value/b.weight, (double)a.value/a.weight));results[0] = greedySolution(items, capacity);});Thread t2 = new Thread(() -> {Arrays.sort(items, (a, b) -> b.value - a.value);results[1] = greedySolution(items, capacity);});Thread t3 = new Thread(() -> {Arrays.sort(items, (a, b) -> a.weight - b.weight);results[2] = greedySolution(items, capacity);});t1.start(); t2.start(); t3.start();try { t1.join(); t2.join(); t3.join(); } catch (InterruptedException e) {}return Math.max(results[0], Math.max(results[1], results[2])); }
实际应用场景
多重背包问题在现实中有广泛应用:
- 资源分配:服务器资源分配,选择最有价值的任务组合
- 投资组合:在有限资金下选择不同数量的多种投资产品
- 生产计划:原材料切割,最大化利用原材料
- 广告投放:在有限广告位中选择不同频次的广告组合
- 运输装载:货车装载多种货物,考虑数量限制
变种问题与扩展
1. 多维多重背包
每种物品有多个维度的重量限制(如体积和重量):
class MultiDimensionalItem {int[] weights; // 各维度的重量int value;int count;
}public static int multiDimensionalGreedy(MultiDimensionalItem[] items, int[] capacities) {// 按综合价值密度排序Arrays.sort(items, (a, b) -> {double densityA = a.value / Arrays.stream(a.weights).sum();double densityB = b.value / Arrays.stream(b.weights).sum();return Double.compare(densityB, densityA);});int[] remaining = Arrays.copyOf(capacities, capacities.length);int totalValue = 0;for (MultiDimensionalItem item : items) {boolean canTakeMore = true;while (canTakeMore) {// 检查是否可以再取一个当前物品for (int i = 0; i < remaining.length; i++) {if (remaining[i] < item.weights[i]) {canTakeMore = false;break;}}if (canTakeMore && item.count > 0) {totalValue += item.value;item.count--;for (int i = 0; i < remaining.length; i++) {remaining[i] -= item.weights[i];}} else {break;}}}return totalValue;
}
2. 分组多重背包
物品分为若干组,每组只能选择一定数量的物品:
class Group {Item[] items;int maxSelect; // 该组最多选择的物品数
}public static int groupKnapsack(Group[] groups, int capacity) {// 两层贪心:先对组排序,再对组内物品排序Arrays.sort(groups, (a, b) -> {double maxDensityA = Arrays.stream(a.items).mapToDouble(item -> (double)item.value/item.weight).max().orElse(0);double maxDensityB = Arrays.stream(b.items).mapToDouble(item -> (double)item.value/item.weight).max().orElse(0);return Double.compare(maxDensityB, maxDensityA);});int remaining = capacity;int totalValue = 0;for (Group group : groups) {Arrays.sort(group.items, (a, b) -> Double.compare((double)b.value/b.weight, (double)a.value/a.weight));int groupSelected = 0;for (Item item : group.items) {if (groupSelected >= group.maxSelect) break;if (remaining <= 0) break;int maxTake = Math.min(item.count, remaining / item.weight);maxTake = Math.min(maxTake, group.maxSelect - groupSelected);if (maxTake > 0) {totalValue += maxTake * item.value;remaining -= maxTake * item.weight;groupSelected += maxTake;}}}return totalValue;
}
测试与验证
测试用例设计
应包含以下类型测试用例:
- 基础用例:简单验证功能
- 边界用例:空输入、单个物品、容量为0等
- 性能用例:大量物品测试算法效率
- 极端用例:所有物品重量相同、价值相同等特殊情况
JUnit测试示例
import org.junit.Test;
import static org.junit.Assert.*;public class MultipleKnapsackTest {@Testpublic void testEmptyItems() {Item[] items = {};assertEquals(0, MultipleKnapsack.greedySolution(items, 10));}@Testpublic void testZeroCapacity() {Item[] items = {new Item(2, 10, 3),new Item(3, 5, 2)};assertEquals(0, MultipleKnapsack.greedySolution(items, 0));}@Testpublic void testSingleItemWithinCapacity() {Item[] items = {new Item(5, 20, 2)};assertEquals(40, MultipleKnapsack.greedySolution(items, 10));}@Testpublic void testSingleItemExceedCapacity() {Item[] items = {new Item(15, 30, 3)};assertEquals(0, MultipleKnapsack.greedySolution(items, 10));}@Testpublic void testMixedItems1() {Item[] items = {new Item(2, 10, 3),new Item(3, 5, 2),new Item(5, 15, 2),new Item(7, 7, 3),new Item(1, 6, 4)};// 贪心解:3个重量1价值6 + 3个重量2价值10 = 3*6 + 3*10 = 48assertEquals(48, MultipleKnapsack.greedySolution(items, 15));}@Testpublic void testMixedItems2() {Item[] items = {new Item(10, 60, 1),new Item(20, 100, 1),new Item(30, 120, 1)};// 最优解是取重量20和30的物品assertEquals(220, MultipleKnapsack.hybridSolution(items, 50));}@Testpublic void testLargeInput() {// 生成100个随机物品测试性能Item[] items = new Item[100];for (int i = 0; i < 100; i++) {int w = 1 + (int)(Math.random() * 10);int v = 1 + (int)(Math.random() * 20);int c = 1 + (int)(Math.random() * 5);items[i] = new Item(w, v, c);}int capacity = 100;long start = System.nanoTime();int result = MultipleKnapsack.greedySolution(items, capacity);long end = System.nanoTime();System.out.println("Large test result: " + result);System.out.println("Time taken: " + (end - start)/1e6 + " ms");assertTrue(result > 0);}
}
算法选择指南
在实际应用中如何选择算法:
- 小规模问题(n < 100,W < 1000):使用动态规划获取精确解
- 中规模问题(100 < n < 10^4):使用贪心+动态规划混合方法
- 大规模问题(n > 10^4):使用纯贪心算法或并行贪心
- 实时系统:必须使用纯贪心算法保证响应时间
- 离线处理:可以使用更复杂的混合方法
常见问题与解决
-
贪心解与最优解差距大:
- 尝试多种贪心策略取最大值
- 结合局部动态规划
- 调整物品排序标准
-
性能瓶颈:
- 预处理移除无用物品
- 限制动态规划的物品范围
- 使用并行计算
-
整数溢出:
- 使用long类型存储大数值
- 添加边界检查
-
浮点精度问题:
- 使用分数比较代替浮点数
- 实现自定义比较器
总结
多重背包问题是组合优化中的经典问题,贪心算法提供了高效的近似解决方案。Java实现时需要注意:
- 物品排序策略的选择
- 贪心与动态规划的平衡
- 性能与解质量的权衡
- 各种边界条件的处理
通过合理选择和改进贪心策略,可以在大多数实际应用中获得满意的结果。对于需要精确解的小规模问题,可以结合动态规划;对于大规模问题,贪心算法是唯一可行的选择。
关键点总结:
- 价值密度优先的贪心策略通常效果最好
- 混合方法可以平衡效率和质量
- 预处理和剪枝能显著提高性能
- 测试要充分,特别是边界情况
更多资源:
https://www.kdocs.cn/l/cvk0eoGYucWA
本文发表于【纪元A梦】!
相关文章:

贪心算法应用:多重背包启发式问题详解
贪心算法应用:多重背包启发式问题详解 多重背包问题是经典的组合优化问题,也是贪心算法的重要应用场景。本文将全面深入地探讨Java中如何利用贪心算法解决多重背包问题。 多重背包问题定义 **多重背包问题(Multiple Knapsack Problem)**是背包问题的变…...

【保姆级教程】PDF批量转图文笔记
如果你有一个PDF文档,然后你想把它发成图文笔记emmm,最好再加个水印,你会怎么做? 其实也不麻烦,打开PDF文档,挨个截图,然后打开PS一张一张图片拖进去,再把水印图片拖进去࿰…...
Pytest Fixture 是什么?
Fixture 是什么? Fixture 是 Pytest 测试框架的核心功能之一,用于为测试函数提供所需的依赖资源或环境。它的核心目标是: ✅ 提供测试数据(如模拟对象、数据库记录) ✅ 初始化系统状态(如配置、临时文件&a…...
Spring Boot 基础知识全面解析:快速构建企业级应用的核心指南
一、Spring Boot 概述:重新定义 Java 开发 1.1 什么是 Spring Boot? Spring Boot 是基于 Spring 框架的快速开发框架,旨在简化 Spring 应用的初始搭建及开发过程。它通过 「约定优于配置」(Convention Over Configuration&#…...

数据库系统概论(十一)SQL 集合查询 超详细讲解(附带例题表格对比带你一步步掌握)
数据库系统概论(十一)SQL 集合查询 超详细讲解(附带例题表格对比带你一步步掌握) 前言一、什么是集合查询?二、集合操作的三种类型1. 并操作2. 交操作3. 差操作 三、使用集合查询的前提条件四、常见问题与注意事项五、…...
[mcu]系统频率
系统主频的选择直接影响性能、功耗和成本,不同厂商的芯片会根据应用场景设计不同的运行频率。 低频段80MHZ~160MHz 典型频率: 80MHz、120MHz、160MHz 特点: 低功耗,适合电池供电设备 处理能力有限,通常仅支持 单天线…...

clickhouse如何查看操作记录,从日志来查看写入是否成功
背景 插入表数据后,因为原本表中就有数据,一时间没想到怎么查看插入是否成功,因为对数据源没有很多的了解,这时候就想怎么查看下插入是否成功呢,于是就有了以下方法 具体方法 根据操作类型查找,比如inse…...

5G-A:开启通信与行业变革的新时代
最近,不少细心的用户发现手机信号标识悄然发生了变化,从熟悉的 “5G” 变成了 “5G-A”。这一小小的改变,却蕴含着通信技术领域的重大升级,预示着一个全新的通信时代正在向我们走来。今天,就让我们深入了解一下 5G-A&a…...
鸿蒙OS在UniApp中集成Three.js:打造跨平台3D可视化应用#三方框架 #Uniapp
在UniApp中集成Three.js:打造跨平台3D可视化应用 引言 在最近的一个项目中,我们需要在UniApp应用中展示3D模型,并实现实时交互功能。经过技术选型和实践,我们选择了Three.js作为3D渲染引擎。本文将分享我们在UniApp中集成Three.…...
Vue 3 组件化设计实践:构建可扩展、高内聚的前端体系
Vue 3 自发布以来,其引入的 Composition API 与改进的组件模型,为前端架构提供了更强的可组合性、复用性与模块化能力。本文将系统性探讨 Vue 3 如何通过组件化设计,实现复杂应用的解耦、扩展与维护,并结合实际工程经验提供最佳实…...
腾讯云 Python3.12.8 通过yum安装 并设置为默认版本
在腾讯云服务器上,直接通过 yum 安装 Python 3.12.8 可能不可行,因为标准仓库通常不包含最新的 Python 版本。不过,我们可以通过添加第三方仓库或手动安装 RPM 包的方式实现。以下是完整解决方案: 方法 1: 通过第三方仓库安装&am…...
鸿蒙OSUniApp页面切换动效实战:打造流畅精致的转场体验#三方框架 #Uniapp
UniApp页面切换动效实战:打造流畅精致的转场体验 引言 在移动应用开发中,页面切换动效不仅能提升用户体验,还能传达应用的品质感。随着HarmonyOS的普及,用户对应用的动效体验要求越来越高。本文将深入探讨如何在UniApp中实现流畅…...
React 泛型组件:用TS来打造灵活的组件。
文章目录 前言一、什么是泛型组件?二、为什么需要泛型组件?三、如何在 React 中定义泛型组件?基础泛型组件示例使用泛型组件 四、泛型组件的高级用法带默认类型的泛型组件多个泛型参数 五、泛型组件的实际应用场景数据展示组件表单组件状态管…...

TDengine 集群运行监控
简介 为了确保集群稳定运行,TDengine 集成了多种监控指标收集机制,并通过 taosKeeper 进行汇总。taosKeeper 负责接收这些数据,并将其写入一个独立的 TDengine 实例中,该实例可以与被监控的 TDengine 集群保持独立。TDengine 中的…...
图像任务中的并发处理:线程池、Ray、Celery 和 asyncio 的比较
在图像缺陷检测任务中,处理大量图像和点云数据时,高效的并发处理是关键。本文将介绍五种流行的并发处理方法:线程池(concurrent.futures.ThreadPoolExecutor)、Ray、Celery、asyncio以及搜狗Workflow,并从原…...
DeepSeek 赋能智能物流:解锁仓储机器人调度的无限可能
目录 一、智能物流仓储机器人调度现状1.1 传统调度面临的挑战1.2 现有智能调度的进展与局限 二、DeepSeek 技术探秘2.1 DeepSeek 核心技术原理2.2 DeepSeek 的独特优势 三、DeepSeek 在智能物流仓储机器人调度中的创新应用3.1 智能任务分配与调度3.2 路径规划与避障优化3.3 实时…...
C#上传图片后压缩
上传的图片尺寸不一,手机拍照的有2000*2000像素的,对实际使用来说 文件尺寸太大,文件也有近4M 下面是直接压缩的方法 1、安装包 Magick.NET-Q16-AnyCPU 2、上代码 /// <summary> /// 缩放图片 /// </summary> /// <param …...

uniapp路由跳转toolbar页面
需要阅读uview-ui的API文档 注意需要使用type参数设置后才起作用 另外route跳转的页面会覆盖toolbar工具栏 toConternt(aid) {console.log(aid:, aid)this.$u.route({// url: "pages/yzpg/detail",url: "pages/yzappl/index",// url: "pages/ind…...

【linux】知识梳理
操作系统的分类 1. 桌⾯操作系统: Windows/macOS/Linux 2. 移动端操作系统: Android(安卓)/iOS(苹果) 3. 服务器操作系统: Linux/Windows Server 4. 嵌⼊式操作系统: Android(底层是 Linux) Liunx介绍 liunx系统:服务器端最常见的操作系统类型 发行版:Centos和Ubuntu 远程连接操…...
PostgreSQL 内置扩展列表
PostgreSQL 内置扩展列表 PostgreSQL 自带了许多内置扩展(built-in extensions),这些扩展提供了额外的功能而不需要额外安装。以下是主要的内置扩展分类和说明: 标准内置扩展(随核心安装) 1. 管理类扩展…...

NodeMediaEdge快速上手
NodeMediaEdge快速上手 简介 NodeMediaEdge是一款部署在监控摄像机网络前端中,拉取Onvif或者rtsp/rtmp/http视频流并使用rtmp/kmp推送到公网流媒体服务器的工具。 通过云平台协议注册到NodeMediaServer后,可以同NodeMediaServer结合使用。使用图形化的…...

ChatOn:智能AI聊天助手,开启高效互动新时代
在当今快节奏的生活中,无论是工作、学习还是日常交流,我们常常需要快速获取信息、整理思路并高效完成任务。ChatOn 正是为满足这些需求而生,它基于先进的 ChatGPT 和 GPT-4o 技术,为用户提供市场上最优秀的中文 AI 聊天机器人。这…...

基于Vue3.0的【Vis.js】库基本使用教程(002):图片知识图谱的基本构建和设置
文章目录 3、图片知识图谱3.1 初始化图片知识图谱3.2 修改节点形状3.3 修改节点背景颜色3.4 完整代码下载3、图片知识图谱 3.1 初始化图片知识图谱 1️⃣效果预览: 2️⃣关键代码: 给节点添加image属性: const nodes = ref([{id: 1,...
监督学习 vs 无监督学习:AI两大学习范式深度解析
监督学习 vs 无监督学习:AI两大学习范式深度解析 引言:机器如何"学习"? 想象教孩子识别动物:一种方法是展示图片并告诉名称(监督学习),另一种是让孩子自己观察动物特征并分类&#…...

C# Costura.Fody 排除多个指定dll
按照网上的说在 FodyWeavers.xml 里修改 然后需要注意的是 指定多个排除项 不是加 | 是换行 一个换行 就排除一项 我测试的 <?xml version"1.0" encoding"utf-8"?> <Weavers xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&quo…...
NodeJS全栈WEB3面试题——P8项目实战类问题(偏全栈)
📦 8.1 请描述你做过的 Web3 项目,具体技术栈和你负责的模块? 我主导开发过一个基于 NFT 的数字纪念平台,用户可以上传照片并生成独特的纪念 NFT,结合 IPFS 和 ERC-721 实现永存上链。 🔧 技术栈…...
小白的进阶之路系列之五----人工智能从初步到精通pytorch张量
张量 张量是一种特殊的数据结构,与数组和矩阵非常相似。在PyTorch中,我们使用张量来编码模型的输入和输出,以及模型的参数。 张量类似于NumPy的ndarray,除了张量可以在gpu或其他硬件加速器上运行。事实上,张量和NumPy数组通常可以共享相同的底层内存,从而消除了复制数据…...

设计模式——迭代器设计模式(行为型)
摘要 本文详细介绍了迭代器设计模式,这是一种行为型设计模式,用于顺序访问集合对象中的元素,同时隐藏集合的内部结构。文章首先定义了迭代器设计模式并阐述了其核心角色,包括迭代器接口、具体迭代器、容器接口和具体容器。接着&a…...

android-studio-2024.3.2.14如何用WIFI连接到手机(给数据线说 拜拜!)
原文:Android不用数据线就能调试真机的方法—给数据线说 拜拜!(adb远程调试) android-studio-2024.3.2.14是最新的版本,如何连接到手机,可用WIFI,可不用数据线,拜拜 第一步…...
[特殊字符] xbatis 一款好用 ORM 框架 1.8.8-M2 发布,节省 1/3 代码和时间的框架!!!
1.8.8-M2 更新内容: 1:优化默认值,对同一类减少重复调用2:优化分页,支持 limit (-1) 进行忽略分页3:优化 UpdateChain.set;支持.set (SysUser::getVersion, c -> c.plus (1))4:优化 @Fetch, 已增强,无法配置 groupby、forceUseIn(已去除)5:增强 @Fetch,支持中间…...