回溯法解01背包问题(最通俗易懂,附C++代码)_回溯法解决01背包问题-程序员宅基地

技术标签: 算法  C++  c++  

问题描述:

01背包问题是算法中的经典问题,问题描述如下:
对于给定的N个物品,第i个物品的重量为Wi,价值为Vi,对于一个最多能装重量C的背包,应该如何选择放入包中的物品,使得包中物品的总价值最大?

回溯法简介:

回溯法的本质其实就是一种蛮力法,只是通过一定的方法可以使得蛮力法中的一些基本情况可以提前排除从而提高蛮力算法效率,回溯可以理解为排除这些不满足条件的基本情况的过程。

回溯法求解0-1背包问题的过程:

由于直接描述过程比较抽象,因此直接上例题
例题:假设N=3(有三件物品),三个物品的重量为{20,15,10},三个物品的价值为{20,30,25},对于一个最大承重为25的背包,求包中物品的组合最大的价值是多少?
题目的参考思路图如下,建议图文对照:(参考书为王红梅老师的《算法设计与分析》)
在这里插入图片描述
对三件物品分别进行编号1,2,3,初始情况背包是空的
1.首先我们把1号物品放进背包里,此时背包里只有一件物品,总重量为0+20=20,没有超过承重25,因此可以将1号物品成功放入背包内;
2.接下来尝试把2号物品放入背包内,但是发现包中1号物品和2号物品的重量和为20+15=35,超过了承重25,因此不能把2号物品放入背包内;
3.接着考虑3号物品,此时包中只有1号物品。发现1号物品和3号物品的重量和为20+10=30,超过了承重25,因此3号物品也不能放入背包内;
4.由于只有3件物品,并且对于每一种物品我们都考虑过是否将其放入背包内,也就是找到了一种基本情况。找到一个基本情况后,我们就可以看看包里的物品的总价值了。这里包里只有一个1号物品,因此总价值为20;
5.重点来了!回溯过程:每次找出一种满足条件的基本情况就进行一次回溯,找到最后放入包中的物品并将其取出,接着考虑是否放入编号在这个物品之后的第一个物品。这里我们就把1号物品取出,接下来考虑是否放入2号物品;
6.取出1号物品后背包是空的,此时如果放入2号物品,背包总重量为15,没有超过背包承重,因此把2号物品放入背包内;
7.类似地,考虑将3号物品放入背包内。由于2号物品和3号物品的重量和为15+10=25,没有超过承重25,因此将其放入背包内;
8.由于考虑完了3号物品,因此又找到了一个基本情况,记下此时包里物品的总价值,为30+25=55。由于55高于上一种基本情况的总价值,因此将最优解更新为55
9.进行一次回溯,取出背包中最后放入的物品,也就是3号物品,但是注意:当最后放入背包中的物品恰好是编号最大的物品时,需要额外进行一次回溯。为什么呢?因为编号最大的物品之后已经没有编号更大的物品了,因此没有可以考虑的下一种情况,只能在上一个层面上在进行一次回溯才能产生可能的最优解。(此处不必考虑只放入2号物品的情况,因为一定不是最优解,原因可以自己思考一下) 这里再回溯一次,也就是将倒数第二个放入包中的物品取出来,这里就取出2号物品。先后取出3号物品和2号物品之后包应该处于空的状态。
10.上一步中取出了2号物品,因此这一步直接考虑能否放入3号物品,简单的判断后即可得出可以放入,并且同上理也可以得出这是一种基本情况,但是由于包中只有3号物品,总价值为25,没有超过当前的最优解55,因此将该情况忽略。
11.最后一次回溯,取出包中的3号元素。由于此时包已经空了,并且最后一次取出的是编号最大的元素,那么说明算法已经完成了所有情况的遍历,算法终止,55是最优解。

程序代码:

#include<iostream>
#include<stack>//由于问题中的包是需要进行后进先出的操作,因此考虑到使用栈这种数据结构(后进先出)
using namespace std;

int maxValue(int w[], int v[], const unsigned& length, const unsigned& capacity)
{
    
    stack<int> Bag;//首先构造出一个空的背包
    int max = 0;//不同装入情况中背包中物品最大的价值
    int weight = 0;//当前背包中物品的总重量
    int value = 0;//当前背包中物品的总价值
    int i;

    for (i = 0; ; i++)
    {
    
        if (weight + w[i] <= capacity)//第一种情况:背包装入该物品后不会超重
        {
    
            Bag.push(i);//将该物品装入背包中
            weight += w[i];//装入物品后背包重量增加
            value += v[i];//装入物品后背包中物品的价值增加
        }
        else//第二种情况:装入该物品后背包超重,则不能将该物品装入包中,相当于什么也不做
        {
    
            //此处用else语句只是方便理解这是另外一种情况,可以省略
        }

        //当从第一个物品考虑到了最后一个物品时,就确定了一个可以满足条件的装包方法(一个方法就是确定了一个规定每一个物品是否装进包里的策略)
        if (i == length - 1)
        {
    
            //接下来将本次装包物品价值与当前最高的装包物品价值进行比较,保留较大的一个
            if (max < value)
            {
    
                max = value;
            }
            //此时取出最后装进包里的一件物品并对其下一件物品进行考虑(这就是算法的重点:回溯的过程!)
            {
    
                i = Bag.top();//取出上一件装入的东西(这就是回溯的过程)
                Bag.pop();
                weight -= w[i];//取出东西后背包重量减轻
                value -= v[i];//取出物品后背包总价值也会降低
                if (i == length - 1)//特殊情况:如果最后装进包里的物品本来在就是编号最大的物品,那么它后面就没有其他物品了,循环必定终止
                {
    
                    if (Bag.empty())break;//当所有物品都被从包中拿出来后,这是说明已经遍历完所有情况,查找结束(相当于解空间树中最右边的子树已经走完了)
                    i = Bag.top();//取出上一件装入的东西(这就是回溯的过程)
                    Bag.pop();
                    weight -= w[i];//取出东西后背包重量减轻
                    value -= v[i];//取出物品后背包总价值也会降低
                }
            }
        }
    }
    return max;
}

int main(void)
{
    
    unsigned num, capacity;
    cout << "请输入物品的个数:";
    cin >> num;
    int* weights = new int[num];
    int* values = new int[num];
    cout << "请输入每件物品的重量:";
    for (unsigned i = 0; i < num; i++)
    {
    
        cin>>weights[i];
    }
    cout << "请输入每件物品的价值:";
    for (unsigned i = 0; i < num; i++)
    {
    
        cin >> values[i];
    }
    cout << "请输入包的最大承重:";
    cin >> capacity;
    cout << "该问题的最优解为:" << maxValue(weights, values, num, capacity);
    return 0;
} 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hanmo22357/article/details/124371395

智能推荐

Codeforces Round #530 (Div. 1) 1098A Sum in the tree_cf1098a sum in the tree-程序员宅基地

文章浏览阅读2.4k次。A. Sum in the treeMitya has a rooted tree with nn vertices indexed from 11 to nn, where the root has index 11. Each vertex vv initially had an integer number av≥0av≥0 written on it. For every vertex ..._cf1098a sum in the tree

C++——std::String-程序员宅基地

文章浏览阅读2.1k次。link写在前面这一篇博客系统学习一下C++中String类的相关函数。这个类在之前做题的时候就经常遇到,其实说白了,它也就是一个vector < char >。但是,它又有一些独特的函数,可以在做题的时候简化代码,提高效率。所以在这一篇博客,就根据CPlusPlus官网中< string >中的内容做一个整理。自己整理之外,还有一些优秀的整理资料可供参考:std::string用法总结。string类与头文件包含string即为字符串。string是C++标准库的一个重要_std::string

python中的class Solution(object):的含义与类继承与类、对象概念的详解-程序员宅基地

文章浏览阅读9.1k次,点赞26次,收藏86次。python中的class Solution(object):的含义与类继承与类、对象概念的详解_class solution

读《疯狂的程序员》后感-程序员宅基地

文章浏览阅读1k次。读《疯狂的程序员》后感 花了几天功夫,把《疯狂的程序员》这本书看完了,这本书,是我无意间在校图书馆看到的,出版日期是2008年的,到现在为止已经过去好几年了,作者绝影。是在csdn上连载的博客,不过不知道作者在csdn上的名字是什么,自己搜索找,竟然没找到,很遗憾。书中讲述的是作者从大学时期到工作,再到创业7年时间的精力,说实话,作者的语文功底非常不错,能够生动的刻画

可后悔贪心 -- 解题报告_e. buy low sell high-程序员宅基地

文章浏览阅读244次。感觉普通贪心是每一个维度都是平等的,没有优先级。而可后悔贪心是存在某个维度是不可变的,不能直接用排序或者堆进行维护,常常需要经过某种处理,通过挖掘出题目中关于不可变维度的特殊性质,使其可以用排序或者堆等数据结构进行贪心。可后悔贪心常用堆(priority_queue)进行维护。_e. buy low sell high

AMCL源码解析-程序员宅基地

文章浏览阅读6.5k次,点赞14次,收藏93次。AMCL是ros导航中的一个定位功能包。其实现了机器人在2D平面中基于概率方法的定位系统。该方法使用粒子滤波器来针对已知地图跟踪机器人的位姿。MCL与AMCL的区别它们最重要的区别应该是重采用过程。AMCL在采样过程中仍然会随机的增加小数量的粒子。这一步骤正式为了解决MCL不能处理的重定位问题。当粒子逐渐聚集,其它地方的粒子将慢慢消失。对于MCL来说,如果此时将机器人搬动到另一个地方。此时原来..._amcl源码

随便推点

ACM模式输入输出攻略 | C++篇-程序员宅基地

文章浏览阅读1.4w次,点赞97次,收藏328次。本文内容干货非常非常多,从笔试面试环境的要点,到C++输入输出的具体函数,再到几乎覆盖全部情况的ACM模式写法,最后也给出了链表和二叉树的定义和输入输出。_acm模式

uni-app实现Android分享到微信朋友圈和微信好友_uniapp实现app微信分享好友和朋友圈需要到微信开放平台申请吗-程序员宅基地

文章浏览阅读2.6k次。最近使用uniapp开发app,使用开发环境中使用微信分享功能时可以正常分享到微信好友或者朋友圈,但是发布后提示,经过百度后发现需要在微信开放平台申请。附上步骤在项目中打开manifest.json,点击App模块权限配置,给Share(分享)打勾,给这个App加一个分享权限。点击App SDK配置,进去找到分享,填写appid那么问题来了,appid从哪弄呢?前往微信开放平台https://open.weixin.qq.com/注册,登录创建移动应用按要_uniapp实现app微信分享好友和朋友圈需要到微信开放平台申请吗

使用docker安装部署oracle12.2_docker 官方oracle 12.2安装包-程序员宅基地

文章浏览阅读9.6k次,点赞2次,收藏6次。1. 步骤在Mac上安装docker使用oracle的dockerfile,构建image在docker中运行oracle实例启动,停止oracle docker容器连接数据库 2. 在Mac上安装docker到docker store下载docker-for-mac。我们需要适当调整一下cpu内存分配,如4核CPU,16G内存。 点击reveal in f..._docker 官方oracle 12.2安装包

Javascript 笔记三-程序员宅基地

文章浏览阅读50次。1. 检测变量是否是非数字的方法是? isNaN(变量) 结果:非数字为true。数字为false if嵌套2. switch语法格式? 注意点 switch(表达式){ case 常量表达式: 语句体; break; case 常量表达式: 语句体; break; 。。。 default: ...

Ionic4—UI组件之表单&双向数据绑定_ionic 动态绑定数据-程序员宅基地

文章浏览阅读974次。目录一、概述二、代码示例三、效果图一、概述二、代码示例Angular中ngModel指令实现双向数据绑定,ngFor实现遍历。组件的属性使用动态数据作为参数时,属性名用中括号包裹。单行文本框<ion-list> <ion-item> <ion-label>用户名:</ion-label>..._ionic 动态绑定数据

PyQt5之QDrag拖放按钮小部件学习_pyqt qdrag-程序员宅基地

文章浏览阅读1.1k次。在下面的示例中,我们将演示如何拖放按钮小部件。from PyQt5.Qt import QPushButton, QWidget, QApplicationfrom PyQt5.QtCore import Qt, QMimeDatafrom PyQt5.QtGui import QDragimport sys#按钮类class Button(QPushButton): d..._pyqt qdrag

推荐文章

热门文章

相关标签