部分背包问题

image

核心解题思路:

  1. 金币可以任意分割,所以优先选单位重量价值最高的金币堆,能装多少装多少
  2. 先计算每堆金币的单位价值 = 总价值/总重量
  3. 按单位价值从高到低排序(降序)
  4. 依次装入背包,直到背包装满为止

代码说明

  1. Gold类:封装每堆金币的重量、价值、单位价值,方便排序和计算
  2. 排序:使用Array.sort按单位价值降序排列,保证优先装最值钱的金币
  3. 贪心装入:
  • 如果当前金币堆能完整装入,直接全部装走
  • 如果装不下,就装背包剩余容量的部分,按单位价值计算对应价值
  1. 输出:使用printf("%.2f",totalValue)格式化输出,保留两位小数

代码解释:
Arrays.sort(golds, new Comparator() {
@Override
public int compare(Gold a, Gold b) {
return Double.compare(b.rate, a.rate);
}
});

1.Arrays工具类解释

1.先纠正名字:是 Arrays 不是 Array
Array:Java 里一个反射相关的类,几乎不用
Arrays:是 Java 自带的数组工具类(在 java.util 包)
专门给普通数组提供一堆现成功能:
排序 sort()
转字符串 toString()
二分查找 binarySearch()
填充 fill()
……
记住:操作数组,统一用工具类 Arrays

2. 为什么要用Arrays.sort(数组, 比较器) 调用?
第一点:普通数组本身没有sort方法
你的定义是:
Gold[] golds = new Gold[N];
这是原生数组,它是底层结构,没有自带排序方法
你不能写:
golds.sort(); // 报错!数组没有这个方法
就像:

  • 普通整数、小数,本身没有工具方法
  • 只能靠工具类帮你干活
    第二点:Array.sort是官方封装好的排序算法
    底层帮你写好了:快排、优化排序逻辑
    你只需要:
    传数组 + 传自定义比较规则(Comparator)
    不用自己写冒泡、选择排序,直接调用就行。

3.new Comparator是什么?
Comparator是Java里的比较器接口(需要自己导包:java.util.Comparator),专门用来自定义排序规则
接口里只有一个必须实现的方法:
int compare(T o1, T o2);
作用:告诉程序两个对象谁大谁小,谁该排在前面

new Comparator(){ ... } 是什么写法?
这叫匿名内部类

  • Comparator:指定这是用来比较Gold对象的比较器
  • new 接口(){重写抽象方法}
    直接临时创建一个实现了Comparator接口的对象,不用单独写一个类

4. 为什么要传第二个参数 Comparator?

  • 如果是int[]、double[]基本类型数组:
    int[] arr = {3,1,2};
    Arrays.sort(arr); // 默认升序,不用比较器
  • 如果是自定义对象数组(你写的 Gold 类):
    JVM 根本不知道按什么规则排:
    按重量?按价值?按单位价值?
    所以必须手动传一个 Comparator 比较器,告诉它怎么比大小。

2. return Double.compare(b.rate, a.rate);
在 Java 的排序算法中,compare(a, b) 的返回值决定了它们的位置:

在 compare(a, b) 这个方法里,这两个参数是有明确定义的:

  • a (第一个参数):代表在数组中位置靠前的元素。

  • b (第二个参数):代表在数组中位置靠后的元素。

  • 返回-1:认为 a 小于 b,不交换位置。

  • 返回0:认为 a 等于 b。

  • 返回+1:认为 a 大于 b,交换a和b的位置,b 排在前面。

当你传入对调了传入的两个参数的顺序compare(b,a)实际上是在误导算法
算法还是按照已经定义好的规则来判断
b-a:
返回1,b比a大,交换a和b的位置,结果就是排在后面的b被交换到前面去了
返回-1:b比a小,不交换位置
返回0:相等,不交换位置

  • compare永远用第一个数来比较,因此如果第一个数比第二个数大,且第一个数在左边,那么就会交换到右边,也就是后面,这就是升序排序compare(a,b)
  • 如果第一个数比第二个大,且第一个数在右边,那么就会交换到左边,也就是前面,这就是降序排序compare(b,a)

可以这么理解,方法抓了两个参数进来给我,左边的参数是排前面的,右边的参数是排后面的,我负责把两个参数传给算法,让他根据写好的规则去计算两个人的大小,返回一个数给我,我再把这个数报给上面,上面根据我上报的这个数对这两个参数进行排序,假如我再把这两个参数传给算法的时候偷偷对调了它们两的位置,那么这个时候就会导致算法给我报了一个错误的数,导致上面在排序的时候排反。

2. printf和println的区别
printlnprintf 都是输出,但功能完全不一样!**

  • println:只能普通打印,不能控制小数位数
  • printf:专门用来格式化打印,可以保留 2 位小数、对齐、补 0

题目要求:
输出一个实数,输出两位小数
所以必须用 printf,不能用 println!

System.out.printf("%.2f", totalValue);
意思:
按照 保留两位小数 的格式,输出 totalValue
%.2f 是什么?

  • %f = 输出小数
  • .2 = 保留 2 位

完整Java代码实现如下:

// 导入包(必须写在最开头)
import java.util.Scanner;
import java.util.Arrays;
import java.util.Comparator;

// 定义金币堆类,存储重量、价值、单位价值
class Gold {

int weight; // 总重量
int value; // 总价值
double rate; // 单位重量价值(价值/重量)

// 构造方法
public Gold(int weight, int value) {
this.weight = weight;
this.value = value;
this.rate = (double) value / weight;
}
}

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

// 1. 输入金币堆数量N 和 背包承重T
int N = sc.nextInt();
int T = sc.nextInt();

// 2. 创建数组存储所有金币堆
Gold[] golds = new Gold[N];
for (int i = 0; i < N; i++) {
int m = sc.nextInt(); // 第i堆重量
int v = sc.nextInt(); // 第i堆价值
golds[i] = new Gold(m, v);
}

// 3. 按单位价值 降序 排序(价值高的优先装)
Arrays.sort(golds, new Comparator() {
@Override
public int compare(Gold a, Gold b) {
// 降序排列:b.rate - a.rate
return Double.compare(b.rate, a.rate);
}
});

// 4. 贪心装入背包
double totalValue = 0.0; // 总价值
int remaining = T; // 背包剩余容量

for (Gold g : golds) {
if (remaining <= 0) break; // 背包装满,退出循环

if (g.weight <= remaining) {
// 整堆都能装下
totalValue += g.value;
remaining -= g.weight;
} else {
// 只能装一部分,按比例计算价值
totalValue += g.rate * remaining;
remaining = 0;
}
}

// 5. 输出结果,保留两位小数
System.out.printf("%.2f", totalValue);

sc.close();
}
}

posted @ 2026-05-08 18:16  ZealousMclaren  阅读(20)  评论(0)    收藏  举报