杰瑞科技汇

贪心算法如何解背包问题?Java实现要点?

背包问题简介

背包问题是一个经典的组合优化问题,基本描述是:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,我们如何选择,才能使物品的总价值最大。

贪心算法如何解背包问题?Java实现要点?-图1
(图片来源网络,侵删)

根据物品是否可以分割,背包问题可以分为三类:

  1. 0/1 背包问题:每种物品要么完整地放入背包,要么不放入,不能分割。
  2. 完全背包问题:每种物品都有无限件,可以无限次放入背包。
  3. 分数背包问题:每种物品都可以分割成任意大小,可以只放入物品的一部分。

贪心算法主要适用于分数背包问题,因为它可以处理物品的分割,对于 0/1 背包问题,贪心算法通常无法得到最优解。


贪心算法解决分数背包问题

核心思想

贪心算法在每一步都做出当前看起来最优的选择,期望通过一系列局部最优的选择,最终达到全局最优。

对于分数背包问题,这个“最优”的选择标准是:优先选择“性价比”最高的物品

贪心算法如何解背包问题?Java实现要点?-图2
(图片来源网络,侵删)
  • 性价比 = 物品的价值 / 物品的重量

算法步骤如下:

  1. 计算性价比:计算所有物品的性价比(value / weight)。
  2. 排序:将所有物品按照性价比从高到低进行排序。
  3. 贪心选择
    • 按照排序后的顺序,依次遍历每个物品。
    • 如果当前物品的整个重量都可以放入剩余的背包容量中,则将其全部放入,背包容量减去该物品的重量,总价值加上该物品的价值。
    • 如果当前物品的整个重量无法放入剩余的背包容量中,则将其一部分(即剩余的背包容量)放入,背包容量变为 0,总价值加上这部分的价值。
    • 如果背包容量已经为 0,则算法结束。

为什么贪心算法对分数背包有效?

因为在分数背包中,物品可以被分割,所以我们可以优先装满性价比最高的“空间”,即使一个物品装不下,也可以只装它的一部分,从而保证没有浪费任何高价值的“单位重量”空间,这使得局部最优的选择(选性价比最高的)能够导向全局最优。


Java 代码实现

下面是一个完整的 Java 实现,包括 Item 类、贪心算法实现和主函数测试。

代码结构

  1. Item.java: 一个简单的 Java Bean,用于表示物品的属性(名称、重量、价值)。
  2. FractionalKnapsack.java: 包含贪心算法的核心逻辑。
  3. Main.java: 主类,用于创建物品、调用算法并打印结果。

Item.java

// Item.java
public class Item {
    private String name;
    private double weight;
    private double value;
    public Item(String name, double weight, double value) {
        this.name = name;
        this.weight = weight;
        this.value = value;
    }
    // 计算性价比
    public double getRatio() {
        if (weight == 0) {
            return 0; // 避免除以零
        }
        return value / weight;
    }
    // Getters
    public String getName() { return name; }
    public double getWeight() { return weight; }
    public double getValue() { return value; }
    @Override
    public String toString() {
        return String.format("Item{name='%s', weight=%.2f, value=%.2f, ratio=%.2f}",
                name, weight, value, getRatio());
    }
}

FractionalKnapsack.java

// FractionalKnapsack.java
import java.util.Arrays;
import java.util.Comparator;
public class FractionalKnapsack {
    /**
     * 使用贪心算法解决分数背包问题
     * @param items 物品数组
     * @param capacity 背包的总容量
     * @return 最大可达到的总价值
     */
    public double getMaxValue(Item[] items, double capacity) {
        // 1. 按照性价比从高到低对物品进行排序
        // 使用 Java 8 的 Lambda 表达式进行自定义排序
        Arrays.sort(items, new Comparator<Item>() {
            @Override
            public int compare(Item a, Item b) {
                // 降序排序,b.getRatio() - a.getRatio() 会得到从大到小的顺序
                return Double.compare(b.getRatio(), a.getRatio());
            }
        });
        // 或者更简洁的 Lambda 写法:
        // Arrays.sort(items, (a, b) -> Double.compare(b.getRatio(), a.getRatio()));
        double totalValue = 0.0; // 总价值
        double remainingCapacity = capacity; // 剩余背包容量
        // 2. 遍历排序后的物品列表
        for (Item item : items) {
            if (remainingCapacity == 0) {
                // 背包已满,结束循环
                break;
            }
            // 如果当前物品可以完全放入背包
            if (item.getWeight() <= remainingCapacity) {
                totalValue += item.getValue();
                remainingCapacity -= item.getWeight();
                System.out.println("放入整个物品: " + item.getName() + 
                                 ", 当前价值: " + totalValue + 
                                 ", 剩余容量: " + remainingCapacity);
            } else {
                // 如果当前物品不能完全放入,则放入一部分
                double fraction = remainingCapacity / item.getWeight();
                double addedValue = item.getValue() * fraction;
                totalValue += addedValue;
                remainingCapacity = 0; // 背包被装满
                System.out.println("放入物品 " + item.getName() + " 的 " + 
                                 String.format("%.2f", fraction * 100) + "%" + 
                                 ", 当前价值: " + totalValue + 
                                 ", 剩余容量: " + remainingCapacity);
            }
        }
        return totalValue;
    }
}

Main.java (测试)

// Main.java
public class Main {
    public static void main(String[] args) {
        // 创建物品列表
        Item[] items = {
            new Item("物品A", 10, 60),   // 性价比 = 6.0
            new Item("物品B", 20, 100),  // 性价比 = 5.0
            new Item("物品C", 30, 120)   // 性价比 = 4.0
        };
        double knapsackCapacity = 50; // 背包容量
        System.out.println("初始物品列表:");
        for (Item item : items) {
            System.out.println(item);
        }
        System.out.println("--------------------------------------------------");
        System.out.println("背包容量: " + knapsackCapacity);
        System.out.println("--------------------------------------------------");
        // 创建 FractionalKnapsack 实例并求解
        FractionalKnapsack fk = new FractionalKnapsack();
        double maxValue = fk.getMaxValue(items, knapsackCapacity);
        System.out.println("--------------------------------------------------");
        System.out.println("在背包容量为 " + knapsackCapacity + " 的情况下,可以获得的最大价值为: " + maxValue);
    }
}

运行结果

初始物品列表:
Item{name='物品A', weight=10.00, value=60.00, ratio=6.00}
Item{name='物品B', weight=20.00, value=100.00, ratio=5.00}
Item{name='物品C', weight=30.00, value=120.00, ratio=4.00}
--------------------------------------------------
背包容量: 50.0
--------------------------------------------------
放入整个物品: 物品A, 当前价值: 60.0, 剩余容量: 40.0
放入整个物品: 物品B, 当前价值: 160.0, 剩余容量: 20.0
放入物品 物品C 的 66.67%, 当前价值: 240.0, 剩余容量: 0.0
--------------------------------------------------
在背包容量为 50.0 的情况下,可以获得的最大价值为: 240.0

结果分析

贪心算法如何解背包问题?Java实现要点?-图3
(图片来源网络,侵删)
  1. 算法首先按性价比排序:物品A (6.0) > 物品B (5.0) > 物品C (4.0)。
  2. 先放入整个物品A(重量10),价值60,剩余容量40。
  3. 接着放入整个物品B(重量20),价值100,总价值160,剩余容量20。
  4. 物品C(重量30)无法完全放入,放入其 20/30 = 2/3 的部分,价值增加 120 * (2/3) = 80,总价值 160 + 80 = 240,背包被装满。
  5. 最终最大价值为 240,这是该情况下的最优解。

为什么贪心算法不适用于 0/1 背包问题?

为了说明这一点,我们看一个简单的反例。

场景

  • 背包容量:W = 50
  • 物品列表:
    • 物品A:重量 w1 = 10,价值 v1 = 60 (性价比 = 6)
    • 物品B:重量 w2 = 20,价值 v2 = 100 (性价比 = 5)
    • 物品C:重量 w3 = 30,价值 v3 = 150 (性价比 = 5)

贪心算法的选择过程

  1. 按性价比排序:A (6) > B (5) = C (5),假设 B 和 C 性价比相同,我们按任意顺序,比如先选 B。
  2. 选择物品A,放入背包,剩余容量 50 - 10 = 40,总价值 60
  3. 选择物品B,放入背包,剩余容量 40 - 20 = 20,总价值 60 + 100 = 160
  4. 物品C(重量30)无法放入,结束。
  5. 贪心算法得到的价值:160

真正的最优解

  1. 不选物品A
  2. 选择物品B和物品C,总重量 20 + 30 = 50,正好装满背包。
  3. 最优价值:100 + 150 = 250

贪心算法得到的解(160)远小于最优解(250),因为它在第一步就做出了局部最优的选择(放入性价比最高的A),但这导致它错过了由B和C组合产生的更大的全局最优解,贪心算法无法保证在0/1背包问题中得到最优解,这类问题通常需要使用动态规划来解决。

分享:
扫描分享到社交APP
上一篇
下一篇