贪心算法应用:多重背包启发式问题详解

news2025/7/22 21:13:06

在这里插入图片描述

贪心算法应用:多重背包启发式问题详解

多重背包问题是经典的组合优化问题,也是贪心算法的重要应用场景。本文将全面深入地探讨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
约束条件:

  1. Σ(w_i * x_i) ≤ W 对于i=1到n
  2. 0 ≤ x_i ≤ c_i 且 x_i为整数

关键特性

  1. 组合爆炸:可能的组合数量为Π(c_i+1),直接枚举不可行
  2. NP难问题:没有已知的多项式时间解法
  3. 贪心可行:虽然不能保证最优解,但能得到近似解

贪心算法解决方案

1. 基本贪心策略

三种常见的贪心策略:

  1. 价值优先:按价值降序选择
  2. 重量优先:按重量升序选择
  3. 价值密度优先:按价值/重量(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>() {
            @Override
            public 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. 算法复杂度分析

  1. 排序:O(n log n)
  2. 贪心选择: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是限制的高价值物品数量

性能优化技巧

  1. 物品预处理

    • 移除重量>W的物品
    • 合并相同物品
    • 对价值密度相同的物品进行捆绑
  2. 搜索剪枝

    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);
            }
        }
    }
    
  3. 并行处理

    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. 资源分配:服务器资源分配,选择最有价值的任务组合
  2. 投资组合:在有限资金下选择不同数量的多种投资产品
  3. 生产计划:原材料切割,最大化利用原材料
  4. 广告投放:在有限广告位中选择不同频次的广告组合
  5. 运输装载:货车装载多种货物,考虑数量限制

变种问题与扩展

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;
}

测试与验证

测试用例设计

应包含以下类型测试用例:

  1. 基础用例:简单验证功能
  2. 边界用例:空输入、单个物品、容量为0等
  3. 性能用例:大量物品测试算法效率
  4. 极端用例:所有物品重量相同、价值相同等特殊情况

JUnit测试示例

import org.junit.Test;
import static org.junit.Assert.*;

public class MultipleKnapsackTest {
    
    @Test
    public void testEmptyItems() {
        Item[] items = {};
        assertEquals(0, MultipleKnapsack.greedySolution(items, 10));
    }
    
    @Test
    public void testZeroCapacity() {
        Item[] items = {
            new Item(2, 10, 3),
            new Item(3, 5, 2)
        };
        assertEquals(0, MultipleKnapsack.greedySolution(items, 0));
    }
    
    @Test
    public void testSingleItemWithinCapacity() {
        Item[] items = {new Item(5, 20, 2)};
        assertEquals(40, MultipleKnapsack.greedySolution(items, 10));
    }
    
    @Test
    public void testSingleItemExceedCapacity() {
        Item[] items = {new Item(15, 30, 3)};
        assertEquals(0, MultipleKnapsack.greedySolution(items, 10));
    }
    
    @Test
    public 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 = 48
        assertEquals(48, MultipleKnapsack.greedySolution(items, 15));
    }
    
    @Test
    public 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));
    }
    
    @Test
    public 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);
    }
}

算法选择指南

在实际应用中如何选择算法:

  1. 小规模问题(n < 100,W < 1000):使用动态规划获取精确解
  2. 中规模问题(100 < n < 10^4):使用贪心+动态规划混合方法
  3. 大规模问题(n > 10^4):使用纯贪心算法或并行贪心
  4. 实时系统:必须使用纯贪心算法保证响应时间
  5. 离线处理:可以使用更复杂的混合方法

常见问题与解决

  1. 贪心解与最优解差距大

    • 尝试多种贪心策略取最大值
    • 结合局部动态规划
    • 调整物品排序标准
  2. 性能瓶颈

    • 预处理移除无用物品
    • 限制动态规划的物品范围
    • 使用并行计算
  3. 整数溢出

    • 使用long类型存储大数值
    • 添加边界检查
  4. 浮点精度问题

    • 使用分数比较代替浮点数
    • 实现自定义比较器

总结

多重背包问题是组合优化中的经典问题,贪心算法提供了高效的近似解决方案。Java实现时需要注意:

  1. 物品排序策略的选择
  2. 贪心与动态规划的平衡
  3. 性能与解质量的权衡
  4. 各种边界条件的处理

通过合理选择和改进贪心策略,可以在大多数实际应用中获得满意的结果。对于需要精确解的小规模问题,可以结合动态规划;对于大规模问题,贪心算法是唯一可行的选择。

关键点总结:

  • 价值密度优先的贪心策略通常效果最好
  • 混合方法可以平衡效率和质量
  • 预处理和剪枝能显著提高性能
  • 测试要充分,特别是边界情况

更多资源:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】!

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

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

相关文章

【保姆级教程】PDF批量转图文笔记

如果你有一个PDF文档&#xff0c;然后你想把它发成图文笔记emmm&#xff0c;最好再加个水印&#xff0c;你会怎么做&#xff1f; 其实也不麻烦&#xff0c;打开PDF文档&#xff0c;挨个截图&#xff0c;然后打开PS一张一张图片拖进去&#xff0c;再把水印图片拖进去&#xff0…

数据库系统概论(十一)SQL 集合查询 超详细讲解(附带例题表格对比带你一步步掌握)

数据库系统概论&#xff08;十一&#xff09;SQL 集合查询 超详细讲解&#xff08;附带例题表格对比带你一步步掌握&#xff09; 前言一、什么是集合查询&#xff1f;二、集合操作的三种类型1. 并操作2. 交操作3. 差操作 三、使用集合查询的前提条件四、常见问题与注意事项五、…

clickhouse如何查看操作记录,从日志来查看写入是否成功

背景 插入表数据后&#xff0c;因为原本表中就有数据&#xff0c;一时间没想到怎么查看插入是否成功&#xff0c;因为对数据源没有很多的了解&#xff0c;这时候就想怎么查看下插入是否成功呢&#xff0c;于是就有了以下方法 具体方法 根据操作类型查找&#xff0c;比如inse…

5G-A:开启通信与行业变革的新时代

最近&#xff0c;不少细心的用户发现手机信号标识悄然发生了变化&#xff0c;从熟悉的 “5G” 变成了 “5G-A”。这一小小的改变&#xff0c;却蕴含着通信技术领域的重大升级&#xff0c;预示着一个全新的通信时代正在向我们走来。今天&#xff0c;就让我们深入了解一下 5G-A&a…

TDengine 集群运行监控

简介 为了确保集群稳定运行&#xff0c;TDengine 集成了多种监控指标收集机制&#xff0c;并通过 taosKeeper 进行汇总。taosKeeper 负责接收这些数据&#xff0c;并将其写入一个独立的 TDengine 实例中&#xff0c;该实例可以与被监控的 TDengine 集群保持独立。TDengine 中的…

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 远程连接操…

NodeMediaEdge快速上手

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

ChatOn:智能AI聊天助手,开启高效互动新时代

在当今快节奏的生活中&#xff0c;无论是工作、学习还是日常交流&#xff0c;我们常常需要快速获取信息、整理思路并高效完成任务。ChatOn 正是为满足这些需求而生&#xff0c;它基于先进的 ChatGPT 和 GPT-4o 技术&#xff0c;为用户提供市场上最优秀的中文 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,

C# Costura.Fody 排除多个指定dll

按照网上的说在 FodyWeavers.xml 里修改 然后需要注意的是 指定多个排除项 不是加 | 是换行 一个换行 就排除一项 我测试的 <?xml version"1.0" encoding"utf-8"?> <Weavers xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&quo…

设计模式——迭代器设计模式(行为型)

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

android-studio-2024.3.2.14如何用WIFI连接到手机(给数据线说 拜拜!)

原文&#xff1a;Android不用数据线就能调试真机的方法—给数据线说 拜拜&#xff01;&#xff08;adb远程调试&#xff09; android-studio-2024.3.2.14是最新的版本&#xff0c;如何连接到手机&#xff0c;可用WIFI&#xff0c;可不用数据线&#xff0c;拜拜 第一步&#xf…

js 动画库、2048核心逻辑、面试题add[1][2][3]+4

1、js 动画库 web animation api &#xff08;1&#xff09;初始化代码 hmtl、css 部分 初始化全局背景黑色初始化黄色小球 js 部分 监听全局点击事件创建并添加元素 class"pointer" 的 div 标签 设置 left、top 位置监听动画结束事件&#xff0c;移除该元素 定位小…

华为OD机试真题——书籍叠放(2025B卷:200分)Java/python/JavaScript/C/C++/GO最佳实现

2025 B卷 200分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…

PyTorch-Transforms的使用(二)

对图像进行处理 安装open cv ctrlP 看用法 ToTensor的使用 常见的Transforms 归一化的图片 两个长度为三的数组&#xff0c;分别表示三个通道的平均值和标准差 Resize&#xff08;&#xff09; Compose&#xff08;&#xff09; 合并执行功能&#xff0c;输入进去一个列表&a…

Pytorch知识点2

Pytorch知识点 1、官方教程2、张量&#x1f9f1; 0、数组概念&#x1f9f1; 1. 创建张量&#x1f4d0; 2. 张量形状与维度&#x1f522; 3. 张量数据类型➗ 4. 张量的数学与逻辑操作&#x1f504; 5. 张量的就地操作&#x1f4e6; 6. 复制张量&#x1f680; 7. 将张量移动到加速…

AWS API Gateway 配置WAF(中国区)

问题 需要给AWS API Gateway配置WAF。 AWS WAF设置 打开AWS WAF首页&#xff0c;开始创建和配置WAF&#xff0c;如下图&#xff1a; 设置web acl名称&#xff0c;然后开始添加aws相关资源&#xff0c;如下图&#xff1a; 选择资源类型&#xff0c;但是&#xff0c;我这里出…

【前端面经】百度一面

写在前面&#xff1a;面经只是记录博主遇到的题目。每题的答案在编写文档的时候已经有问过deepseek&#xff0c;它只是一种比较普世的答案&#xff0c;要学得深入还是靠自己 Q&#xff1a; <html><style>.a {background-color: red;width: 200px;height: 100px;}…

[免费]微信小程序网上花店系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序网上花店系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序网上花店系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…