任务描述
设有编号为0、1、2、…、n-1的n个物品,它们的重量分别为w0、w1、…、wn-1,价值分别为p0、p1、…、pn-1,其中wi、pi(0≤i≤n-1)均为正数。
有一个背包可以携带的最大重量不超过W。求解目标:在不超过背包负重的前提下,使背包装入的总价值最大(即效益最大化),与0/1背包问题的区别是,这里的每个物品可以取一部分装入背包。
本关任务:编写一个能计算背包问题的最优解及装入背包的物品总价值的小程序。
问题求解
采用贪心法求解。设xi表示物品i装入背包的情况,0≤xi≤1。根据问题的要求,有如下约束条件和目标函数:
于是问题归结为寻找一个满足上述约束条件,并使目标函数达到最大的解向量X={x0,x1,…,xn-1}。
量度标准:单位重量的价值
每次选择单位重量价值最大的物品进行装入
为了完成本关任务,你需要注意:对物品需要首先按单位重量的价值从大到小排序
编程要求
根据提示,在右侧编辑器补充代码,计算并输出最优解及总价值。
测试说明
执行程序时,数据输入的顺序为:
背包容量:物品个数:各物品的重量和价值:
平台会对你编写的代码进行测试:
测试输入:20,3,18,25,15,24,10,15;
预期输出:
背包容量:物品个数:请分别输入物品的重量和价值:
最优解:0.00 1.00 0.50
总价值:31.50
开始你的任务吧,祝你成功!
代码思路
- 定义物品类:创建一个 Item 类,包含物品的重量、价值以及单位重量的价值(即价值与重量的比值)。此外,我们还需要一个索引来标识物品的顺序,方便输出结果。
- 输入数据:从标准输入读取背包容量、物品数量以及每个物品的重量和价值。
- 排序:根据物品的单位重量价值对物品进行排序,这里使用了
Comparable
接口来实现自定义排序。 - 填充背包:按照单位价值从高到低的顺序逐个尝试将物品装入背包,直到背包容量不足以容纳下一个物品的全部重量为止。
- 输出结果:输出最优解(即每个物品装入背包的比例)以及总价值
首先定义一个 Item
类,它包含了物品的重量、价值、单位重量价值和索引。
class Item implements Comparable<Item> {
double weight; // 物品的重量
double value; // 物品的价值
double ratio; // 单位重量的价值
int index; // 物品的索引,方便输出
// 构造方法 初始化索引、背包的重量、价值
public Item(int index, double weight, double value) {
this.index = index;
this.weight = weight;
this.value = value;
this.ratio = value / weight; // 计算单位重量的价值
}
@Override
public int compareTo(Item o) {
return Double.compare(o.ratio, this.ratio);
}
}
主类与输入数据
在主类 GreedyKnapsack
中,创建 Scanner
对象用于读取输入数据,并读取背包容量 W 和物品数量 n
public class GreedyKnapsack {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("背包容量:");
double W = scanner.nextDouble(); // 读取背包容量
System.out.print("物品个数:");
int n = scanner.nextInt(); // 读取物品个数
Item[] items = new Item[n]; // 初始化物品数组
System.out.print("请分别输入物品的重量和价值:");
for (int i = 0; i < n; i++) {
double weight = scanner.nextDouble(); // 读取物品的重量
double value = scanner.nextDouble(); // 读取物品的价值
items[i] = new Item(i, weight, value); // 创建 Item 对象并存储在数组中
}
排序物品
使用 Arrays.sort()
方法对物品数组进行排序,按照单位重量价值降序排列。
Arrays.sort(items); // 对物品数组进行排序
计算最优解及总价值
遍历排序后的物品数组,尝试装入物品,直到背包容量不足为止。
double totalValue = 0.0; // 初始化总价值
double[] solution = new double[n]; // 初始化解向量
for (int i = 0; i < n && W > 0; i++) {
Item item = items[i];
if (W - item.weight >= 0) { // 如果当前物品可以完全装入背包
solution[item.index] = 1.0; // 标记为完全装入
totalValue += item.value; // 累加价值
W -= item.weight; // 更新剩余容量
} else { // 如果当前物品不能完全装入背包
solution[item.index] = W / item.weight; // 计算可装入的比例
totalValue += item.value * (W / item.weight); // 累加部分价值
break; // 退出循环
}
}
输出结果
输出每个物品装入背包的比例以及总的经济价值。
System.out.println();
System.out.print("最优解:");
for (int i = 0; i < n; i++) {
System.out.printf("%.2f ", solution[i]); // 输出每个物品装入的比例
}
System.out.println(); // 换行
System.out.printf("总价值:%.2f\n", totalValue); // 输出总价值
scanner.close(); // 关闭 Scanner 对象,释放资源
}
}
完整代码
package step2;
import java.util.Scanner;
import java.util.Arrays;
class Item implements Comparable<Item> {
double weight; // 物品的重量
double value; // 物品的价值
double ratio; // 单位重量的价值
int index; // 物品的索引,方便输出
public Item(int index, double weight, double value) {
this.index = index;
this.weight = weight;
this.value = value;
this.ratio = value / weight; // 计算单位重量的价值
}
@Override
public int compareTo(Item o) {
return Double.compare(o.ratio, this.ratio);
}
}
public class GreedyKnapsack {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("背包容量:");
double W = scanner.nextDouble(); // 读取背包容量
System.out.print("物品个数:");
int n = scanner.nextInt(); // 读取物品个数
Item[] items = new Item[n]; // 初始化物品数组
System.out.print("请分别输入物品的重量和价值:");
for (int i = 0; i < n; i++) {
double weight = scanner.nextDouble(); // 读取物品的重量
double value = scanner.nextDouble(); // 读取物品的价值
items[i] = new Item(i, weight, value); // 创建 Item 对象并存储在数组中
}
Arrays.sort(items); // 对物品数组进行排序
double totalValue = 0.0; // 初始化总价值
double[] solution = new double[n]; // 初始化解向量
for (int i = 0; i < n && W > 0; i++) {
Item item = items[i];
if (W - item.weight >= 0) { // 如果当前物品可以完全装入背包
solution[item.index] = 1.0; // 标记为完全装入
totalValue += item.value; // 累加价值
W -= item.weight; // 更新剩余容量
} else { // 如果当前物品不能完全装入背包
solution[item.index] = W / item.weight; // 计算可装入的比例
totalValue += item.value * (W / item.weight); // 累加部分价值
break; // 退出循环
}
}
System.out.println();
System.out.print("最优解:");
for (int i = 0; i < n; i++) {
System.out.printf("%.2f ", solution[i]); // 输出每个物品装入的比例
}
System.out.println(); // 换行
System.out.printf("总价值:%.2f\n", totalValue); // 输出总价值
scanner.close(); // 关闭 Scanner 对象,释放资源
}
}
计算最优解及总价值代码详细解释
totalValue
: 初始化为0,用于累计所选物品的总价值。
solution
: 一个长度为 n 的数组,其中 n 是物品的数量。数组中的每个元素代表对应物品被放入背包的比例。例如,如果某个物品完全放入背包,那么它的索引位置上的值将是1.0;如果是部分放入,那么值就是该物品被放入的比例。
然后进入主循环:
for (int i = 0; i < n && W > 0; i++) {
这个循环将会遍历所有物品,直到背包没有剩余空间或者所有物品都检查过了。
对于每个物品 item
:
Item item = items[i];
我们检查背包是否还有足够的空间来完全装入这个物品:
if (W - item.weight >= 0) {
如果背包还能容纳这个物品:
solution[item.index] = 1.0;
totalValue += item.value;
W -= item.weight;
solution[item.index] = 1.0
表示这个物品被完全装入背包。
totalValue += item.value
将物品的价值加入总价值。
W -= item.weight
更新背包的剩余容量。
否则,如果背包不能容纳整个物品:
else {
solution[item.index] = W / item.weight;
totalValue += item.value * (W / item.weight);
break;
}
solution[item.index] = W / item.weight
计算并记录可以放入背包的物品比例。
totalValue += item.value * (W / item.weight)
将根据比例放入的物品的价值加入总价值。
break
退出循环,因为此时背包已满。
循环代值举例
假设我们有如下物品列表:
背包容量
W=7。
按照价值密度(价值/重量)从大到小排序后,物品的顺序为 [3, 1, 2]。
初始化 totalValue = 0, solution = [0.0, 0.0, 0.0]。
第一次循环:
物品3(价值8,重量5),剩余容量
W=7,可以完全装入:
solution[2] = 1.0
totalValue = 8
W = 2
第二次循环:
物品1(价值6,重量2),剩余容量
W=2,可以完全装入:
solution[0] = 1.0
totalValue = 14
W = 0
此时 W <= 0,跳出循环。
最终的结果是 solution = [1.0, 0.0, 1.0]
,表示物品1和物品3被完全放入背包,而物品2没有被放入。总价值 totalValue
= 14。
最后想说
附上C语言实现背包问题代码:
#include <stdio.h>
#include <string.h>
#define MAXN 51 // 定义最大物品数量为51
// 定义物品结构体
struct NodeType
{
int num; // 物品的索引
double w; // 物品的重量
double p; // 物品的价值
double pw; // 单位重量的价值(价值/重量)
};
// 用于存储物品装入背包的比例
double x[MAXN];
// 按单位利润对物品排序
void Sort(struct NodeType* a, int n)
{
int i, j, k;
struct NodeType t;
for (i = 0; i < n - 1; i++) // 外层循环控制排序轮数
{
k = i; // 假设当前轮最小值的位置
for (j = i + 1; j < n; j++) // 内层循环找到实际最小值的位置
if (a[k].pw < a[j].pw) // 如果找到更大的单位利润值
k = j; // 更新最小值位置
if (k != i) // 如果最小值位置发生变化
{
t = a[i]; // 交换当前元素和找到的最小值
a[i] = a[k];
a[k] = t;
}
}
}
// 贪心算法求解分数背包问题
void GreedyKnapsack(struct NodeType* a, double m, int n, double* max_value)
{
int i, k;
Sort(a, n); // 按照单位利润排序物品
for (i = 0; i < n; i++) // 遍历排序后的物品数组
{
if (a[i].w <= m) // 如果当前物品可以完全装入背包
{
x[a[i].num] = 1.0; // 标记为完全装入
m -= a[i].w; // 更新剩余容量
}
else // 如果当前物品不能完全装入背包
{
x[a[i].num] = m / a[i].w; // 计算可装入的比例
break; // 退出循环
}
}
// 计算总价值
for (i = 0; i < n; i++)
*max_value += a[i].p * x[a[i].num];
}
// 显示物品信息
void disp(struct NodeType* a, int n)
{
int i;
for (i = 0; i < n; i++) // 遍历物品数组
printf(" %d: %.2lf %.2lf %.2lf\n", a[i].num, a[i].w, a[i].p, a[i].pw); // 打印物品的信息
}
int main()
{
struct NodeType a[MAXN]; // 创建物品数组
int i, n = 0; // 初始化物品数量
double W, value = 0.0; // 背包容量和总价值
printf("背包容量:"); // 提示输入背包容量
scanf("%lf", &W); // 读取背包容量
printf("物品个数:"); // 提示输入物品个数
scanf("%d", &n); // 读取物品个数
printf("请分别输入物品的重量和价值:\n"); // 提示输入物品的重量和价值
for (i = 0; i < n; i++) // 遍历物品数组
{
a[i].num = i; // 设置物品的索引
scanf("%lf", &a[i].w); // 读取物品的重量
scanf("%lf", &a[i].p); // 读取物品的价值
a[i].pw = a[i].p / a[i].w; // 计算单位重量的价值
}
// 对解向量初始化
for (i = 0; i < n; i++)
x[i] = 0;
// disp(a, n); // 可选:显示物品信息(调试用)
GreedyKnapsack(a, W, n, &value); // 求解最优解
// 输出最优解
printf("最优解:");
for (int i = 0; i < n; i++)
printf("%.2f ", x[i]); // 输出每个物品装入的比例
// 输出总价值
printf("\n总价值:%.2lf\n", value); // 输出总价值
return 0; // 返回0表示正常结束
}
背包问题比较难,考试直接把代码背下来就好了。