【算法设计与分析】第2关:背包问题

news2025/2/25 11:58:02

任务描述

设有编号为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

开始你的任务吧,祝你成功!

代码思路

  1. 定义物品类:创建一个 Item 类,包含物品的重量、价值以及单位重量的价值(即价值与重量的比值)。此外,我们还需要一个索引来标识物品的顺序,方便输出结果。
  2. 输入数据:从标准输入读取背包容量、物品数量以及每个物品的重量和价值。
  3. 排序:根据物品的单位重量价值对物品进行排序,这里使用了 Comparable 接口来实现自定义排序。
  4. 填充背包:按照单位价值从高到低的顺序逐个尝试将物品装入背包,直到背包容量不足以容纳下一个物品的全部重量为止。
  5. 输出结果:输出最优解(即每个物品装入背包的比例)以及总价值

首先定义一个 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表示正常结束
}

背包问题比较难,考试直接把代码背下来就好了。

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

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

相关文章

C++类和对象——第三关

在阅读此篇文章之前&#xff0c;请先阅读博主之前的文章&#xff1a; C类和对象第一关-CSDN博客 C类和对象——第二关-CSDN博客 以便更好的理解本文章。 目录 运算符重载 &#xff08;一&#xff09;运算符重载 &#xff08;二&#xff09;赋值类运算符函数的重载&#x…

基于EBAZ4205矿板的图像处理:16基于小波变换的图像分解及其重建

基于EBAZ4205矿板的图像处理&#xff1a;17基于小波变换的图像分解及其重建 特别说明 这个项目的代码不会开源&#xff0c;因为这个项目的一大部分是在实习的公司做的&#xff0c;所以仅提供思路和展示&#xff0c;展示一下我的能力。 先看效果 这次让小牛和小绿做模特 经过…

C++模板初阶,只需稍微学习;直接起飞;泛型编程

&#x1f913;泛型编程 假设像以前交换两个函数需要&#xff0c;函数写很多个或者要重载很多个&#xff1b;那么有什么办法实现一个通用的函数呢&#xff1f; void Swap(int& x, int& y) {int tmp x;x y;y tmp; } void Swap(double& x, double& y) {doubl…

胤娲科技:AI短视频——创意无界,即梦启航

在这个快节奏的时代&#xff0c;你是否曾梦想过用几秒钟的短视频&#xff0c;捕捉生活中的每一个精彩瞬间&#xff1f;是否曾幻想过&#xff0c;即使没有专业的摄影和剪辑技能&#xff0c;也能创作出令人惊艳的作品&#xff1f; 现在&#xff0c;这一切都不再是遥不可及的梦想。…

微前端学习以及分享

微前端学习以及分享 注&#xff1a;本次分享demo的源码github地址&#xff1a;https://github.com/rondout/micro-frontend 什么是微前端 微前端的概念是由ThoughtWorks在2016年提出的&#xff0c;它借鉴了微服务的架构理念&#xff0c;核心在于将一个庞大的前端应用拆分成多…

从MySQL到OceanBase离线数据迁移的实践

本文作者&#xff1a;玉璁&#xff0c;OceanBase 生态产品技术专家。工作十余年&#xff0c;一直在基础架构与中间件领域从事研发工作。现负责OceanBase离线导数产品工具的研发工作&#xff0c;致力于为 OceanBase 建设一套完善的生态工具体系。 背景介绍 在互联网与云数据库技…

LEAP 瞬移工具场景试点游戏关卡

你是否厌倦了在Unity编辑器中浪费时间浏览大型游戏关卡&#xff1f;不要看得比Leap更远&#xff01;这个功能强大的编辑器脚本允许您只需单击一下即可即时传输到场景中的任何位置。告别繁琐的手动导航&#xff0c;迎接闪电般快速的关卡设计。有了Leap&#xff0c;你就可以专注于…

Gin框架官方文档详解04:HTTP/2 推送,JSON相关

官方文档&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;强烈建议没用过Gin的读者先阅读第一节&#xff1a;第一个Gin应用。 目录 一、HTTP/2 推送二、JSONP三、PureJSON四、SecureJSON五、总结 一、HTTP/2 推送 首先&#xff0c;以“04HTTP2server推送”为根目…

linux 时区问题

一、修改系统时间和时区 查看当前下系统时间和时区 timedatectl设置系统时区 ​sudo timedatectl set-timezone <时区>​&#xff0c;例如&#xff1a; sudo timedatectl set-timezone Asia/Shanghai执行成功则没有输出。 更推荐使用 tzselect​ ​命令&#xff0c;…

通信工程学习:什么是VHDL超高速集成电路硬件描述语言

VHDL&#xff1a;超高速集成电路硬件描述语言 VHDL&#xff0c;全称为Very-High-Speed Integrated Circuit Hardware Description Language&#xff0c;即超高速集成电路硬件描述语言&#xff0c;是一种用于电路设计的高级语言。以下是关于VHDL的详细介绍&#xff1a; 一、起源…

ThingsBoard规则链节点:Split Array Msg节点详解

引言 拆分数组消息节点简介 用法 含义 应用场景 实际项目运用示例 智能仓储管理系统 智能电网监控系统 车联网平台 结论 引言 ThingsBoard是一个功能强大的物联网平台&#xff0c;它提供了设备管理、数据收集与处理以及实时监控等核心功能。其规则引擎允许用户定义复杂…

时序图分析(IIC通信为例)

一、时序图分析&#xff08;IIC通信为例&#xff09; 时序图-->编程 解析&#xff1a;时序概念&#xff1a;一般指可编程器件的编程方法&#xff0c;在单片机编程时&#xff0c;需要根据被控芯片的时序去写程序&#xff0c;把芯片上的时序用代码来实现&#xff0c;方可实…

数据结构4——栈

1. 栈的概念及结构 栈的概念&#xff1a; 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则…

<Linux> 线程池

一、线程池 1. 池化技术 池化技术是一种在计算机科学中广泛应用的优化技术&#xff0c;它的核心思想是&#xff1a;预先创建并维护一组资源&#xff08;例如线程、连接、对象&#xff09;&#xff0c;供多个任务共享使用&#xff0c;以减少创建和销毁资源的开销&#xff0c;提…

贪吃蛇游戏(代码篇)

我们并不是为了满足别人的期待而活着。 前言 这是我自己做的第五个小项目---贪吃蛇游戏&#xff08;代码篇&#xff09;。后期我会继续制作其他小项目并开源至博客上。 上一小项目是贪吃蛇游戏&#xff08;必备知识篇&#xff09;&#xff0c;没看过的同学可以去看看&#xf…

使用Java API访问Apache Kafka

简介 Kafka是由Apache软件基金会开发的一个开源流处理平台,Kafka是一个功能强大且灵活的平台。 基本情况 软件名称:Apache Kafka 软件平台:跨平台 软件语言:Scala、Java 开发商:Apache软件基金会 软件授权:Apache License 2.0 最近更新时间:2024年7月23日 核心概念 -…

Ubuntu:用户不在sudoers文件中

1、问题 执行sudo xxx命令时&#xff0c;显示&#xff1a; user 不在sudoers文件中 需要查看系统版本进入恢复模式修复。 2、重启进入恢复模式 查看系统命令&#xff1a;uname -r 可能显示为&#xff1a;6.8.0-45-generic 重启Ubuntu系统&#xff0c;在开机时按ESC进入模…

oracle归档日志爆满问题处理

最近客户单位的oracle数据库出了问题&#xff0c;经常出现无法连接,报错提示 ORA-00257: archiver error, Connect internal only, until freed.&#xff0c;手动清除归档日志后可以恢复访问&#xff0c;但是过不了几天依旧会爆满&#xff0c;每日生成的归档日志很大。经过详细…

内部排序算法小结

练习目标&#xff1a; 1、实现 直接插入排序、冒泡排序、SHELL排序和快速排序&#xff1b; 2、随机生成100组数据&#xff0c;每组数据1000个元素。 注意&#xff1a;计时的单位是CPU的clock而非时间&#xff01;&#xff01;&#xff01; 【后续】 1、加入选择排序&#xff1b…

读书笔记《PPT演讲力》大树模型

作者把PPT演讲比作一棵大树&#xff0c;树的每一部分对应着PPT演讲的一个技巧。 根据这个大树模型&#xff0c;是否有联想到自己过往的演讲经历&#xff1f;演讲是否都达到了大树模型中说的效果&#xff1f;根据这个思维导图&#xff0c;结合自己的经历&#xff0c;试着总结3句…