回溯法-0/1背包问题

news2024/12/23 0:46:25

什么是回溯法?

回溯法是一种搜索算法,它通过深度优先搜索的方式来解决决策问题。它从根节点开始,逐步扩展节点,直到找到所有可能的解。

回溯法的基本思想

  1. 开始节点:从根节点出发,这个节点是解空间的起点。
  2. 扩展节点:在当前节点上,选择一个方向继续搜索,这个方向会形成一个新的节点。
  3. 活节点与死节点:如果新节点有更多选择,它就是活节点;如果所有选择都已尝试,它就是死节点。
  4. 回溯:如果当前路径不是解,回溯到上一个活节点,尝试其他选择。

优化方法

为了提高搜索效率,我们使用两种剪枝技术:

  1. 剪枝一:如果当前物品的加入使得总重量超过背包容量,停止搜索这个方向。
  2. 剪枝二:如果剩余物品即使全部加入,也不能超过当前已知的最优解,停止搜索这个方向。

什么是0/1背包问题?

想象一下,你有一个背包,容量有限。你面前有n种不同的物品,每种物品都有自己的重量和价值。你的目标是选择一些物品放入背包,使得背包里物品的总价值最大,但总重量不能超过背包的容量。

算法描述

给定物品数量n,物品i的重量是wi>0,其价值为vi>0,背包的容量为c。我们的目标是找到一种物品组合,使得总价值最大,且总重量不超过c。

步骤

  1. 初始化:设置一个数组或列表来记录选择的物品。
  2. 递归函数:定义一个递归函数,接收当前物品索引、当前重量和当前价值作为参数。
  3. 边界条件:如果当前重量超过背包容量或已处理完所有物品,回溯。
  4. 选择与不选择:对于每种物品,尝试选择它或不选择它,然后递归调用函数。
  5. 更新最优解:每次找到一个解时,比较并更新已知的最优解。

0/1背包问题算法设计

算法目标

我们的目标是找出一个解向量 ( xi ),其中 ( xi = 0 ) 表示不放入物品 ( i ),( xi = 1 ) 表示放入物品 ( i )。

递归函数 Backtrack

  1. 叶子节点:如果 ( i > n ),我们到达了一个新的物品装包方案,更新最优价值。
  2. 扩展节点:如果 ( i < n ),当前节点在排列树的第 (i-1) 层,递归搜索子树,剪去不满足约束的节点。

实例

输入

  • 物品价值 ( V = {12, 11, 9, 8} )
  • 物品重量 ( W = {8, 6, 4, 3} )
  • 背包容量 ( B = 13 )

可行解

  • 解1: ( x = <0, 1, 1, 1> ) 选入物品2, 3, 4,总价值28,总重量13
  • 解2: ( x = <1, 0, 1, 0> ) 选入物品1, 3,总价值21,总重量12

最优解

  • 最优解: ( x = <0, 1, 1, 1> )

定义

  • ( CW )(Current Weight): 当前重量
  • ( CP )(Current Price): 当前价值

执行步骤

  1. 计算单位价值:降序排列物品。
  2. 从根节点出发:根节点代表当前扩展节点。
  3. 搜索左子树:判断物品是否装入背包。
    • 可行,更新 ( CW ) 和 ( CP ),继续遍历。
    • 不可行,回溯,尝试右子树。
  4. 计算上界 ( bound(i) ):
    • 若 ( bound(i) < bestp ),剪枝。
    • 否则,继续搜索。
  5. 叶子节点:比较 ( CP ) 与 ( bestp ),更新 ( bestp )。
  6. 遍历所有节点:完成搜索。

举例说明

已知 ( p = {45, 25, 24} ), ( w = {16, 15, 15} ), 背包容量为30,求最优价值。

步骤1:计算单位价值并排序

首先,我们需要计算每个物品的单位价值,即每个物品的价值除以其重量。然后,我们将物品按照单位价值从高到低进行排序。在这个例子中,物品的原始顺序恰好是单位价值降序排列。

步骤2:开始遍历并判断是否装入背包

我们将按照排序后的物品顺序,逐个考虑每个物品是否装入背包。

遍历过程说明

  • B1, B2:代表同一种物品B的不同节点。
  • C1, C2, C3, C4:代表同一种物品C的不同节点。
  • 这样的表示方法有助于我们区分在遍历过程中的不同节点。

具体步骤

  1. 考虑物品B1:首先尝试将物品B1装入背包。如果B1的重量加上当前背包重量(CW)不超过背包总容量(本例中为30),则B1可以装入背包。此时,背包重量更新为CW = 16,背包价值更新为CP = 45。

  2. 遍历B1的左子树:继续考虑B1后面的物品,如果剩余容量不足以装入下一个物品,我们就剪去这条路径。

  3. 进入B1的右子树:如果B1可以装入,我们继续考虑其他物品。在右子树中,我们到达物品C2,并计算上界值bound(i)。如果bound(i)大于当前最优价值bestp,则继续向下遍历。

  4. 到达叶子节点:如果在遍历中到达叶子节点,我们比较当前价值(CP)与最优价值(bestp),如果CP更大,则更新最优价值。

  5. 回溯:如果发现某条路径不可能产生更好的解,我们回溯到上一个决策点,尝试其他可能性。

示例遍历

  • 装入物品B1,CW = 16, CP = 45。
  • 尝试装入物品C1,但因剩余容量不足而剪枝。
  • 继续考虑C2,计算bound(i)=45+(25/15)**14=45+1.66*14=68.3,大于当前最优价值45,继续遍历。
  • 到达C2的叶子节点,记录最优价值bestp = 45。
  • 回溯,尝试其他物品B2,更新bound(i)为49,继续遍历。
  • 装入物品C3,CW = 15, CP = 25,继续考虑下一个物品。
  • 装入物品D5,CW = 30, CP = 49,更新最优价值bestp = 49。
  • 继续回溯,考虑其他可能的组合直到所有节点遍历完毕。

结果

经过所有可能的遍历和回溯,我们发现最优的背包装载方案价值为49,对应的物品组合为CD。

遍历过程图示

在这里插入图片描述
bound = 45+14*(24/15)=67.4

代码

#include <iostream> // 引入标准输入输出流库
#include <stdio.h>  // 引入C标准库,提供输入输出函数
using namespace std; // 使用标准命名空间

// 定义全局变量
int n; // 物品数量
double c; // 背包容量
double v[100]; // 各个物品的价值数组
double w[100]; // 各个物品的重量数组
double cw = 0.0; // 当前背包重量
double cp = 0.0; // 当前背包中物品的总价值
double bestp = 0.0; // 记录找到的最优价值
double perp[100]; // 存储物品的单位价值,用于排序
int order[100]; // 存储物品的原始索引,用于排序后恢复
int put[100]; // 标记每个物品是否被选中放入背包,1表示放入,0表示不放入

// 按单位价值对物品进行排序的函数
void knapsack() {
    int i, j; // 循环变量
    int temporder = 0; // 用于交换的临时变量
    double temp = 0.0; // 用于交换的临时变量

    // 计算每个物品的单位价值并存放到数组perp中
    for(i = 1; i <= n; i++) {
        perp[i] = v[i] / w[i];
    }

    // 使用冒泡排序算法按单位价值对物品进行排序
    for(i = 1; i <= n - 1; i++) {
        for(j = i + 1; j <= n; j++) {
            // 如果当前物品的单位价值小于下一个物品,则交换它们的位置
            if(perp[i] < perp[j]) {
                // 交换perp数组中的元素
                temp = perp[i];
                perp[i] = perp[j];
                perp[j] = temp;

                // 交换order数组中的元素,以保持物品原来的顺序
                temporder = order[i];
                order[i] = order[j];
                order[j] = temporder;

                // 交换v数组中的元素,以保持物品价值的一致性
                temp = v[i];
                v[i] = v[j];
                v[j] = temp;

                // 交换w数组中的元素,以保持物品重量的一致性
                temp = w[i];
                w[i] = w[j];
                w[j] = temp;
            }
        }
    }
}

// 回溯函数,用于搜索最优解
void backtrack(int i) {
    // i表示当前正在考虑的物品索引
    if(i > n) { // 如果已经考虑完所有物品,则结束递归
        bestp = cp; // 更新最优价值为当前价值
        return;
    }
    // 如果当前物品可以放入背包,更新背包状态并继续搜索左子树
    if(cw + w[i] <= c) {
        cw += w[i]; // 将物品重量加到当前背包重量
        cp += v[i]; // 将物品价值加到当前背包价值
        put[i] = 1; // 标记当前物品已放入背包
        backtrack(i + 1); // 递归搜索下一件物品
        // 回溯,撤销上一步操作
        cw -= w[i];
        cp -= v[i];
        put[i] = 0;
    }
    // 计算当前扩展节点的上界,如果上界大于当前最优价值,则继续搜索右子树
    double boundValue = bound(i + 1);
    if(boundValue > bestp) {
        backtrack(i + 1);
    }
}

// 计算上界函数,用于剪枝以减少搜索空间
double bound(int i) {
    // 计算剩余背包容量
    double leftw = c - cw;
    double b = cp; // 当前背包的总价值
    // 遍历剩余物品,尝试以单位价值递减的顺序装入背包
    while(i <= n && w[i] <= leftw) {
        leftw -= w[i]; // 更新剩余容量
        b += v[i]; // 更新总价值
        i++; // 移动到下一个物品
    }
    // 如果还有剩余容量,尝试用最大单位价值的物品填充
    if(i <= n) {
        b += (v[i] / w[i]) * leftw;
    }
    return b; // 返回计算出的上界
}

// 主函数,程序入口点
int main() {
    int i; // 循环变量
    // 从用户那里获取物品数量和背包容量
    printf("请输入物品的数量和背包的容量:");
    scanf("%d %lf", &n, &c);

    // 从用户那里获取每个物品的重量
    printf("请依次输入%d个物品的重量:\n", n);
    for(i = 1; i <= n; i++) {
        scanf("%lf", &w[i]);
        order[i] = i; // 初始化物品的原始索引
    }

    // 从用户那里获取每个物品的价值
    printf("请依次输入%d个物品的价值:\n", n);
    for(i = 1; i <= n; i++) {
        scanf("%lf", &v[i]);
    }

    // 调用排序函数和回溯函数
    knapsack();
    backtrack(1);

    // 输出最优价值和需要装入背包的物品编号
    printf("最优价值为:%lf\n", bestp);
    printf("需要装入的物品编号是:");
    for(i = 1; i <= n; i++) {
        if(put[i] == 1) {
            printf("%d ", order[i]);
        }
    }
    printf("\n"); // 输出换行符,美化输出格式
    return 0; // 程序正常结束
}

在这里插入图片描述

时间复杂度

因为物品只有选与不选2个决策,而总共有n个物品,所以时间复杂度为在这里插入图片描述

因为递归栈最多达到n层,而且存储所有物品的信息也只需要常数个一维数组,所以最终的空间复杂度为O(n)。

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

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

相关文章

LLM 教程——如何为特定任务定制微调 BERT

通过本文&#xff0c;您将学会如何为特定的自然语言处理任务&#xff08;如分类、问答等&#xff09;微调BERT。 1、引言 BERT 是一个强大的预训练语言模型&#xff0c;可以用于多种下游任务&#xff0c;只需进行极小的修改。通过微调 BERT&#xff0c;您可以利用它的大规模知…

系统设计:一致性哈希的概念

目录 一、介绍 二、问题提出 三、朴素实施 四、一致性哈希 4.1 关闭服务器 4.2 添加新服务器 五、分布不均 5.1 虚拟节点 5.2 应用 六、结论 资源 一、介绍 我们生活在一个每天都会生成大量数据的世界里。在大公司中&#xff0c;几乎不可能将所有数据存储在单个服务器…

【区间dp、前缀和】 P1220 关路灯 题解

关路灯 题目描述 某一村庄在一条路线上安装了 n n n 盏路灯&#xff0c;每盏灯的功率有大有小&#xff08;即同一段时间内消耗的电量有多有少&#xff09;。老张就住在这条路中间某一路灯旁&#xff0c;他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。 为了给村里…

APACHE NIFI—wait、notify组件报拒绝连接访问的报错

报错文字&#xff1a; Wait[idele44704-6fb6-1b60-ffff-ffffdcofbba2]Failed to process session due to Failed to get signal for c84c4aec-1287-4216-b1a2-f5c6fod4a3b7 due to java.net.ConnectException:Connection refused: org.apache.nifi.processor.exception.Proces…

jmeter响应断言、json断言、断言持续时间操作

一、响应断言 Apply to&#xff1a;断言应用的范围&#xff0c;这里默认&#xff0c;通常发出一个请求只触发一个服务器测试字段 响应文本&#xff0c;response响应体内的信息响应代码&#xff1a; 响应码&#xff0c;一般是200响应信息&#xff1a;响应码后面的返回的信息&am…

zdppy+vue3+onlyoffice文档管理系统实战 20240831上课笔记 继续完善登录功能

遗留的问题 1、整合验证码的接口2、渲染验证码3、实现验证码校验的功能4、验证码校验通过之后&#xff0c;再校验登录功能 验证码框架怎么使用 安装&#xff1a; pip install zdppy_captcha使用示例&#xff1a; import zdppy_api as api import zdppy_captcha import zdp…

Docker compose 安装 ELK

1. 简介 方案概述 我们使用 Filebeat 作为日志收集器&#xff0c;接入到 Redis 队列&#xff0c;然后消费队列中的日志数据流转到 Logstash 中进行解析处理&#xff0c;最后输出到 Elasticsearch 中&#xff0c;再由 Kibana 展示到页面上。我们采用 Elasticsearch 3 节点集群…

hello树先生——AVL树

AVL树 一.什么是AVL树二.AVL树的结构1.AVL树的节点结构2.插入函数3.旋转调整 三.平衡测试 一.什么是AVL树 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。…

【计组 | Cache原理】讲透Cache的所有概念与题型方法

Cache 写在前面&#xff1a;高速缓存Cache一直408中的重点以及绝对的难点&#xff0c;前几天我在复习计组第三章的知识&#xff0c;Cache这一节把我困住了&#xff0c;我发现很多概念我都不记得了&#xff0c;一些综合性强的计算题根本无从下手&#xff0c;我深知Cache对于每个…

1分钟把高质量AI知识库站点嵌入小程序

许多企业都有把 AI 知识库装进小程序、网站、企业微信、钉钉等的需求&#xff0c;让用户能够在小程序上访问到高品质的内容。奈何有太多限制&#xff0c;往往会遇到IP地址不被信任或技术对接接口配置等困难。HelpLook能帮你节省这些繁琐的程序&#xff0c;0代码快速将AI知识库站…

工程师们都爱看的Docker容器技术,一看就会!保姆级教程(上)

文章目录 Docker简介Docker在企业中的应用场景Docker与虚拟化的对比Docker的优势 部署Docker部署DockerDocker的基本操作Docker镜像管理容器的常用操作 Docker镜像构建Docker镜像结构镜像运行的基本原理镜像获得方式镜像构建Docker镜像构建企业实例 镜像优化方案镜像优化策略镜…

一款免费强大的快速启动工具,快速打开程序,软件,网站,工具等

Lucy是一款由个人开发者针对个人需求开发的快速启动工具&#xff0c;其最大的特点在于简洁和快速。它允许用户通过简单的拖拽操作将文件、文件夹、网址等添加到启动列表中&#xff0c;实现快速访问常用程序和文件的目的。Lucy不依赖于网络连接&#xff0c;避免了隐私泄露的风险…

Xcode插件开发

Xcode插件开发 文章目录 Xcode插件开发一、插件开发流程创建插件Extension文件介绍文件说明 二、插件使用安装说明 一、插件开发流程 创建插件的过程并不复杂&#xff0c;只是官方教程&#xff0c;过于简单&#xff0c;所以这里补充下创建细节 创建插件 环境&#xff1a;Xco…

公安智慧大楼信息化整体建设设计方案

1. 项目背景与需求分析 《公安智慧大楼信息化整体建设设计方案》针对一个用地面积和建筑面积均具规模的建设项目&#xff0c;提出了信息化建设的全方位设计方案&#xff0c;以满足现代公安业务的需求。 2. 信息化设计理念 方案强调了信息化设计的顶层设计方法论&#xff0c;…

【Qt】窗口概述

Qt 窗口概述 Qt窗口是由QMianWindow类来实现的。 QMainWindow 是⼀个为⽤⼾提供主窗⼝程序的类&#xff0c;继承⾃ QWidget 类&#xff0c;并且提供了⼀个预定义的布局。QMainWindow 包含 ⼀个菜单栏&#xff08;menu bar&#xff09;、多个⼯具栏(tool bars)、多个浮动窗⼝&a…

基于Swagger自动生成离线API文档(Word、Markdown文档)

在做项目时通常需要给客户提供离线Word的API文档归档&#xff0c;不要跟客户说有Swagger在线API文档&#xff0c;客户不会用也不会去看。只要你有Swagger&#xff0c;TableGo就能自动生成一份漂亮的Word离线API文档给客户&#xff0c;大大提高了写文档的效率&#xff0c;客户看…

【0-1背包】3180. 执行操作可获得的最大总奖励 I

给你一个整数数组 rewardValues&#xff0c;长度为 n&#xff0c;代表奖励的值。 最初&#xff0c;你的总奖励 x 为 0&#xff0c;所有下标都是 未标记 的。你可以执行以下操作 任意次 &#xff1a; 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。 如果 rewardValues[i] 大…

openwrt结合智能家居(相关搜索:路由器)

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

如何在华为平板上开发一个web网页

首先进入华为应用市场下载Qpython 下载完成后打开,在主页里面进入pip,然后下载flask 下载完成后创建一个Python文件,输入 然后运行,进入了Qpython终端后输入pyth,然后就可以开始开发web网页了。 请各位老手们多多指点

10:USB模块布局

1.先处理差分线 先开上方飞线&#xff1a; 发现有个晶体&#xff1a; ①晶体有随身电容&#xff0c;随身电阻 ②晶体布局思路&#xff1a; –电容和电阻放在ic和晶体之间 –整个晶体的部分靠近IC – 信号走向&#xff1a;IC—>电阻—>电容(左右两边摆)—>晶体 开右…