当前位置:   article > 正文

回溯算法设计(2):回溯法解决0/1背包问题_0/1背包回溯法算法设计

0/1背包回溯法算法设计

问题描述

有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。

设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且满足重量限制具有最大的价值
bi在这里插入图片描述

显然0/1背包问题的解空间是一棵子集树。

求解问题

装入背包中的物品重量和恰好为W
  • 用w[1…n]/v[1…n]存放物品信息,x[1…n]数组存放最优解,其中每个元素取1或0,x[i]=1表示第i个物品放入背包中,x[i]=0表示第i个物品不放入背包中。
  • 这是一个求最优解问题。找到更优解(op,tv) ➡️ (x,maxv)

对第i层上的某个分枝结点,对应的状态为dfs(i,tw,tv,op),其中tw表示装入背包中的物品总重量,tv表示背包中物品总价值,op记录一个解向量。该状态的两种扩展如下:

在这里插入图片描述
在这里插入图片描述

  • 叶子结点表示已经对n个物品做了决策
  • 对所有叶子结点进行比较求出满足tw=W的最大maxv,对应的最优解op存放到x中。
改进
  • 左剪枝:对于第i层的有些结点,tw+w[i]已超过了W,显然再选择w[i]是不合适的。应仅仅扩展tw+w[i]<=W的左孩子结点。

  • 右剪枝:rw=w[i]+w[i+1]+…+w[n]
    当不选择物品i时:rw-w[i]=w[i+1]+…+w[n],若tw+rw-w[i]<W,也就是说即使选择后面的所有物品,重量也不会达到W,因此不必要再考虑扩展这样的结点。仅仅扩展满足tw+rw-w[i]≥W的右孩子结点。

代码表示

void dfs(int i,int tw,int tv,int rw,int op[])
{  //初始调用时rw为所有物品重量和
   int j;
   if (i>n)				//找到一个叶子结点
   {  if (tw==W && tv>maxv) 		//找到一个满足条件的更优解,保存
      {  maxv=tv;
         for (j=1;j<=n;j++)		//复制最优解
            x[j]=op[j];
      }
   }
   else					//尚未找完所有物品
   {  if (tw+w[i]<=W)			//左孩子结点剪枝
      {  op[i]=1;			//选取第i个物品
         dfs(i+1,tw+w[i],tv+v[i],rw-w[i],op);
      }
      if (  tw+rw-w[i]>=W )//右剪枝
      {  op[i]=0;			 //不选取第i个物品,回溯
          dfs(i+1,tw,tv,rw-w[i],op);
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
算法分析

该算法不考虑剪枝时解空间树中有2n+1-1个结点(剪枝的结点个数不确定),所以最坏情况下算法的时间复杂度为O(2^n)。

装入背包中物品重量和不超过W
方案
  • 左剪枝方式不变
  • 右剪枝改为采用上界函数进行
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
int bound(int i,int tw,int tv)	//求上界
{  i++;					//从i+1开始
   while (i<=n && tw+A[i].w<=W)	//若序号为i的物品可以整个放入
   {  tw+=A[i].w;
      tv+=A[i].v;
      i++;
   }
   if (i<=n)
       return tv+(W-tw)*A[i].p;	//序号为i的物品不能整个放入
   else
       return tv;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
剪枝
  • 左剪枝:仅仅扩展tw+w[i]≤W的左孩子结点
  • 右剪枝:仅仅扩展bound(i,tw,tv)>maxv的右孩子结点。一旦找到一个解后,后面找到的其他解(tv,op)只能越来越优

代码表示

void dfs(int i,int tw,int tv,int op[])  //求解0/1背包问题
{  if (i>n)				   //找到一个叶子结点
   {  maxv=tv;				   //存放更优解
      for (int j=1;j<=n;j++)
         x[j]=op[j];
   }
   else					   //尚未找完所有物品
   {  if (tw+A[i].w<=W)  		   //左孩子结点剪枝
      {  op[i]=1;			   //选取序号为i的物品
         dfs(i+1,tw+A[i].w,tv+A[i].v,op);
      }
      if (bound(i,tw,tv)>maxv)	   //右孩子结点剪枝
      {  op[i]=0;			   //不选取序号为i的物品,回溯
         dfs(i+1,tw,tv,op);
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
算法分析

该算法不考虑剪枝时解空间树中有2n+1-1个结点(剪枝的结点个数不确定),所以最坏情况下算法的时间复杂度为O(2^n)。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/333062
推荐阅读
相关标签
  

闽ICP备14008679号