部分背包问题

核心解题思路:
- 金币可以任意分割,所以优先选单位重量价值最高的金币堆,能装多少装多少
- 先计算每堆金币的单位价值 = 总价值/总重量
- 按单位价值从高到低排序(降序)
- 依次装入背包,直到背包装满为止
代码说明:
- Gold类:封装每堆金币的重量、价值、单位价值,方便排序和计算
- 排序:使用Array.sort按单位价值降序排列,保证优先装最值钱的金币
- 贪心装入:
- 如果当前金币堆能完整装入,直接全部装走
- 如果装不下,就装背包剩余容量的部分,按单位价值计算对应价值
- 输出:使用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的区别
println 和 printf 都是输出,但功能完全不一样!**
- 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();
}
}
浙公网安备 33010602011771号