基于回溯法求解0-1背包问题

这篇具有很好参考价值的文章主要介绍了基于回溯法求解0-1背包问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、实验目的

1.掌握基于回溯的算法求解0-1背包问题的原理。

2.掌握编写回溯法求解0-1背包问题函数的具体步骤并理解回溯法的核心思想以及其求解过程。

3.掌握子集树以及其他几种解空间树的回溯方法并具备运用回溯算法的思想设计算法并用于求解其他实际应用问题的能力。

4.深刻体会回溯算法求解问题的便利以及感受使用回溯算法所编写程序的明确结构和良好的可读性。

5.从算法设计分析角度,体验用不同解法(动态规划,回溯法)求解问题的方法和思路,从而对0-1背包问题基于回溯法求解有更进一步的理解。

二、实验环境

操作系统:Windows10

文本编辑器:VisualStudio Code

所用语言和编译器:C++ g++

实验终端:WindowsPowerShell

三、实验内容

现在给定N个物品,每个物品的重量为w(即物品i的重量为wi),给定一个背包,背包的容量为c,0-1背包问题要解决的是,通过选择装入背包中的物品,使得所选择物品的重量之和w在小于等于背包容量的情况下,使得装入背包的物品的价值为所有选择之中最大的那个。

0-1背包问题有如下限制,对于每种物品i,有两种选择,装入或不装如(装入为1,不进行装入为0),既不能装入多次,也不能只装入一部分。

I

1

2

3

4

W(重量)

3

5

2

1

P(价值)

9

10

7

4

例如若当前背包容量为9,有4个物体,各个物体的重量和价值如上图所示:则通过分析可知,最优解为装入价值为9,10,4的物品,最优价值为23,下面将通过回溯算法实现求解该01背包问题。

程序输入,首先输入物品的数量n和背包的容量c,并依次输入n个物品的重量wi和每个物品的价值pi,程序要求输出为可以得到的最优价值和达到最优值所装入的物品0-1序列。

四、算法描述

通过分析问题可知,0-1背包问题为整数线性规划中的01规划问题,通过建模,可以得出01背包问题的形式化描述如下 :给定c > 0, wi > 0, vi > 0, 1<= i <=n,所求为

基于回溯法求解0-1背包问题

对于回溯算法,将这n个物品装入背包,每个物品对应两个状态,装入背包或不装入背包。第i中物品对应(x1,x2,…,xn),其中xi可以取0或1,分别表示将该物品装入背包或不装入背包。解空间有2^n种可能的解法,也就是n个元素组成的集合所有子集的个数,该集合可以采用一棵满二叉树将该解空间组织起来,解空间的深度为问题的规模n。

使用回溯法求解0-1背包问题需要通过回溯枚举所有的解空间,如果不通过剪枝以减少计算量的话,枚举所有解空间所需要的时间复杂度为O(2^n),总共有n个物品,按顺序依次考虑每个物品,这样就形成了一棵解空间树。如图所示:

基于回溯法求解0-1背包问题

然而进一步分析可知,很多情况下的回溯是没有意义的,比如当物品重量大于背包容量时,没有必要再进一步对剩余的物品进行回溯决策了,同时若剩下的所有物品都取其总价值也没有当前已经求得的物品选择方案价值大,也可以停止回溯,即该回溯算法求解0-1背包问题存在优化空间,可以利用剪枝来减少计算量并降低时间复杂度。

构造出问题的状态空间树以后,就可以从其根节点出发来搜索解空间,为使目标函数值增加的最快,可以每次优先选择价值最大的物品装入背包,直到背包装不下,但是若选择的物品重量很大,则背包剩余空间消耗的太快,又使得继续装入背包的物品在满足约束方程的要求后,却无法达到目标函数的要求。

为解决这个问题,首先应该把所有的物品按价值重量比非递增排序,然后按照这个顺序进行搜索,在装入背包的过程中,尽量优先选择价值重量比较高的物品装入背包。即在搜索过程中,尽量沿着左子树结点前进,当不能继续前进时,就会得到问题的一个部分解(设该节点为T)。然后搜索右子树,估计由该部分分解所能达到的最大价值,即结点T的上限。

对于剩余物品的处理,将按照价值重量比以非递增的顺序依次装入背包,到无法完全装入下一个物品时,就将该物品的一部分装满背包,这样就可以得到一个上限,如果改制为当前最优值,继续从右子树向下搜索,以扩大部分解直到找到可行解,保存可行解,并把可行解的值作为当前最优值,向上回溯,寻找其他可行解,若该值小于当前最优值,则丢弃当前正在搜索的部分解,向上回溯。反复使用当前方法,直到搜索完所有解空间。

求解0-1背包问题的回溯函数可以提取为如下几个步骤:

回溯算法(backtrack)

如果到达边界:

记录当前的结果,进行处理

如果没有到达边界:

如果满足限界条件:(左子树)

进行处理

进行下一层的递归求解

将处理回退到处理之前

如果不满足限界条件:(右子树)

进行下一层递归处理

Backtrack函数在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其减去(即剪枝)其具体步骤可以描述如下:

  1. :回溯函数backtrack形参使用i来记录当前回溯所到达的层数,同时也记录的当前记录了几个物品。

  1. :判断是否到达边界,如果到达边界,对bestp进行赋值,并结束递归,回溯函数返回。

  1. :判断左子结点是否可行,若可行则搜索左子树,搜索时将物品i放入背包,并更新当前背包重量和总价值,然后进入下一层的搜索,搜索完毕后回溯复原。

  1. :对于右子树,使用上界函数判断右子树是否符合条件(bound(i+1)>bestp),记录当前put[i] = 0,然后进行右子树的搜索。

代码逻辑如下:

void backtrack(int i)

{ //i用来指示到达的层数和当前选择了哪几个物品

// doublebound(int i);

if(i>n) //递归结束的判定条件

{

bestp = cp;

return;

} //如若左子节点可行,则直接搜索左子树;

//对于右子树,先计算上界函数,以判断是否将其减去

if(cw+w[i]<=c)//将物品i放入背包,搜索左子树

{

put[i] = 1;

cw+=w[i];//更新当前背包的重量

cp+=p[i];//更新当前背包的总价值

backtrack(i+1);//搜索下一层

cw-=w[i];

cp-=p[i];//回溯复原

}

if(bound(i+1)>bestp)//如若符合条件则搜索右子树

{

put[i] = 0;

backtrack(i+1);

}

对于bound函数,该函数用来判断当前背包的总价值cp和背包剩余所可以容纳的最大价值是否小于等于当前的最优价值bestp,同时装入物品时,是以剩余物品单位重量价值递减的次序进行装入。代码逻辑如下:

  1. :使用cleft变量表示当前背包剩余容量(cleft=c-cw)cw为当前背包容量,并使用变量b记录当前背包的总价值cp,用以计算上界。

  1. :以物品单位重量价值递减次序装入物品。

  1. :判断是否装满背包并返回计算出的上界b。

代码逻辑如下:

double bound(int i)

{ //判断当前背包的总价值cp+剩余容量可容纳的最大价值<=当前最优价值

double cleft=c-cw;//剩余背包容量

double b =cp;//记录当前背包的总价值cp,最后求上界

//以物品单位重量价值递减次序装入物品

while(i<=n&& w[i]<=cleft)

{

cleft-=w[i];

b+=p[i];

i++;

}

//装满背包

if(i<=n)

b+=p[i]/w[i]*cleft;

return b;//返回计算出的上界

}

回溯开始前,使用函数sortArr()对价值数组,重量数组,和单位重量价值比函数进行排序。对着三者的排序,可以在一次冒泡排序中同时解决。算法步骤如下:

  1. :计算每个物品的单位价值(单位重量的物品价值)。

② :冒泡按照价值重量比非递增的顺序对价值数组,重量数组和单位价值数组进行排序。

代码逻辑如下:

voidsortArr()

{

for(int i=1;i<=n;i++)

perp[i]=p[i]/w[i]; //计算单位价值(单位重量的物品价值)

for(int i=1;i<=n-1;i++)

{

for(int j=i+1;j<=n;j++)

if(perp[i]<perp[j])

{

swap(perp[i], perp[j]); //交换单位价值数组元素

swap(p[i],p[j]); //交换价值数组元素

swap(w[i],w[j]); //交换重量数组元素

}

}

}

算法的时间复杂度,计算上界函数bound需要O(n)的时间,最坏情况下有O(2^n)个右儿子节点需要计算上界函数,故解决0-1背包问题的回溯算法的时间复杂度为O(n2^n)。

五、实验结果

第一组输入,设输入物品的数量和背包的容量为n = 4,c = 9,各个物品的重量w为3,5,2,1,各个物品的价值p为9,10,7,4。则通过回溯算法搜索迭代求解可知,背包问题的最优价值为23,需要装入的物品编号为1,0,1,1(对应排好序后的物品价值序列),装入的物品为价值为9,4,10。

基于回溯法求解0-1背包问题

第二组输入,设输入物品的数量和背包的容量为n = 4,c = 1000(背包容量远大于各个物品的重量),各个物品的重量w为1,2,3,4,各个物品的价值p为99,99,99,99。则通过回溯算法搜索迭代求解可知,背包问题的最优价值为396,需要装入的物品编号为1,1,1,1(对应排好序后的物品价值序列),即将所有的物品都装入。

基于回溯法求解0-1背包问题

第三组输入,设输入物品的数量和背包的容量为n = 4,c =0(背包容量远小于各个物品的重量),各个物品的重量w为1,2,3,4,各个物品的价值p为100,100,100,100。则通过回溯算法搜索迭代求解可知,背包问题的最优价值为0,所有的物品都无法装入。

基于回溯法求解0-1背包问题

六、实验总结

本次实验从0-1背包问题基于回溯算法求解出发,生动形象的展示了回溯算法在生活中的实用性。关于最0-1背包问题,有多种算法可以求解,比如前面学过的动态规划算法,在动态规划算法中,其时间复杂度为O(nc),在回溯算法求解的实现中,其时间复杂度为O(n2^n),通过两次使用不同的算法求解相同的问题,增加了我对于这两种算法和对0-1背包问题的理解。

在本次的代码实现中,遇到了两个错误,第一个错误是实现sortArr函数时,要对三个数组进行非递增的冒泡排序,在其中价值数组的交换时,误将j写成i,导致结果出错,最后通过debug发现,在排序时,根本没有进行交换,在修改完毕后,为了防止此问题再次出现,我将交换逻辑修改为swap函数实现,以更快的找到错误。

第二个问题是选择是否装入物品的数组的输出问题,由于输出顺序出错,导致选出的最优价值是正确的,而选则的最优数组确实错误的(0和1的个数是正确的,顺序却是错误的),最后经过排查后该问题也得以解决。

通过这次实验,我也深刻感受到使用回溯算法求解相关问题的好处,使用回溯算法求解,代码结构清晰,思路明确且易于理解,并且通过限界函数和约束条件剪枝减少了很多不必要的计算。使用回溯算法求解问题便利高效。

回溯算法的递归通过系统递归栈实现,增加了空间复杂度,但使用递归可以明显减少代码的编写复杂程度,同时使用递归编程时,应注意递归结束条件的编写,防止程序无限运行,造成计算资源的浪费。

通过本次实验,增加了我对限界函数和约束条件剪枝对于减少计算量的认知(求解时间大幅减少),以及在编程中,应根据实际问题出发,通过对比各种实现方式,选取高效简洁同时安全的实现。这些优化空间启发着我不断学习,尝试各种实现和优化,并且对于求解算法问题的道路上遇到的各种问题,应不断求索以实现自我进步。

代码如下:文章来源地址https://www.toymoban.com/news/detail-476728.html

到了这里,关于基于回溯法求解0-1背包问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 回溯法解决01背包问题

    目录 一、分析 (一)定义问题的解空间 (二)确定解空间的组织结构 (三)搜索解空间  1. 约束条件 2. 限界条件 (四)搜索过程 二、举例 三、核心代码 四、完整代码 问题的解是从n个物品中选择一些物品使其在不超过容量的情况下价值最大。每个物品有且只有两种状态

    2024年01月21日
    浏览(34)
  • 回溯算法--01背包问题

    目录 回溯算法--01背包问题 [算法描述] [回溯法基本思想] 法一: 法二:  代码:  运行结果 代码改进  0-1背包问题是子集选取问题。一般情况下,0-1背包问题是NP完全问题。0-1背包问题的解空间可以用子集树表示。 解0-1背包问题的回溯法与解装载问题的回溯法十分相似。 在

    2023年04月09日
    浏览(35)
  • 回溯法:0-1背包问题

    给定种物品和一背包。 物品的重量是, 其价值为,背包的容量为 c。 问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?注意物品不重复! 实例:物品价值V={12, 11, 9, 8}, 物品重量W={8, 6, 4, 3}, 背包容量c=13 结点:向量 ( 子集的部分特征向量) 搜索空间:

    2024年01月20日
    浏览(32)
  • 0-1背包问题(动态规划+回溯法)

    给定n(n=100)种物品和一个背包。物品i的重量是wi(wi=100),价值为vi(vi=100),背包的容量为C(C=1000)。 应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有两个选择:装入或不装入。不能将物品i装入多次,也不能只装入部分

    2024年02月04日
    浏览(38)
  • 回溯法求解八皇后问题

    问题提出 :八皇后问题是一个古老而著名的问题。该问题是十九世纪著名的数学家高斯1850 提出在 8x8 格的国际象棋上摆放八皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志

    2023年04月08日
    浏览(55)
  • 回溯法——求解子集和问题(Java)

    给定n个不同的正整数的集合w={w1,w2,…,wn}和一个正整数W,要求找出w的子集s, 使该子集中所有的元素的和为W 。例如,当n=4时,w={11,13,24,7},W=31,则满足要求的子集为(11,13,7)和(24,7)。 和0/1背包问题一样,该问题的解空间数是一颗子集树。设解向量x=(x1,

    2024年02月06日
    浏览(47)
  • 01背包问题的多种求解

    问题描述: 有一个容量为V的背包,还有n个物体。现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里。每个物体都有两个属性,即体积w和价值v。 使物品装入背包的价值最大。 2.0-1 背包问题的解决思路     1. 方法一:枚举法

    2024年02月16日
    浏览(32)
  • 回溯法解01背包问题(最通俗易懂,附C++代码)

    01背包问题是算法中的经典问题,问题描述如下: 对于给定的N个物品,第i个物品的重量为Wi,价值为Vi,对于一个最多能装重量C的背包,应该如何选择放入包中的物品,使得包中物品的总价值最大? 回溯法的本质其实就是一种蛮力法,只是通过一定的方法可以使得蛮力法中

    2023年04月08日
    浏览(36)
  • HJ16 购物单 - 分组背包问题求解

    题目链接参考 HJ16 购物单_牛客题霸_牛客网 这道题需要通过动态规划来求解,首先先通过 ChatGPT 了解下如何 利用动态规划求解01背包问题和完全背包问题 ,以下是 ChatGPT 的答案 动态规划是什么? 动态规划(Dynamic Programming,DP)是一种常用的算法思想,用于解决多阶段决策问

    2023年04月21日
    浏览(37)
  • java实现0-1背包问题方案(动态规划-贪心算法-回溯-分支定界)

    动态规划算法时间复杂度较低,能够求解较大规模的问题,但空间复杂度较高,不适用于数据量较大的问题。 贪心算法时间复杂度较低,能够求解较大规模的问题,但不能保证求得的解是最优解。 回溯算法能够求解较小规模的问题,但时间复杂度较高,不适用于数据量较大

    2024年02月01日
    浏览(126)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包