回溯法本质上是一种深度优先搜索状态空间树的算法。
假设不引入剪枝函数(约束函数+限界函数)。则是穷举算法。
引入适当的限界函数,剪去已能确信不含最优答案结点的子树,使其成为一种启示式算法。
显示约束:xi=1表示将第i件物品装入背包, xi=0表示第i件物品不装入背包。
隐式约束:
解空间大小为2n 。
解空间树为高度为n+1的满二叉树。
下界函数:变量L初值为0,遇到一个答案结点便计算该答案结点的收益值fp,且令L=max{L,fp}。则L中始终保存迄今为止已经搜索到的答案结点中收益的最大值。0/1背包的最优解值必然大于等于L,因此最优解值的下界预计值为L。
限界函数:状态空间树中任一结点X,若其上界函数值bp<最优解值的下界预计值变量L。则可断定X子树上不含最优答案结点。能够剪去以X为根的子树。
剪去不含最优解的分枝【怎么都小于已经搜索到的答案节点收益最大值】
上界函数:当前位于状态空间树的结点X处,cw为背包当前重量。cp为当前已装入背包物品的总收益。则贪心法求解剩余载重和剩余物品构成的一般背包问题(物品编号k+1,k+2,...,n-1,载重M-cw)最大收益为rp。则以X为根的子树上全部可能答案结点的目标函数值 不可能超过bp=cp+rp。因此结点X的上界函数值为bp。
例8-4 设有0/1背包n=8, M=110,(w0,w1,...,w7)=(1,11,21,23,33,43,45,55),
(p0,p1,...,p7)=(11,21,31,33,43,53,55,65)。
——按pi/wi非增排列,即pi/wi≥pi+1/wi+1
总结:向左走减去不可行的解的分支【不满足隐形约束的】 向右走减去不含最优解的【就是不管怎样小于L的】
以下给出我的Java代码
public class Packet { public int []w; private int m; private int n; public int []p; public int fp; public Packet() { w=new int[]{30,10,20,50,40}; p=new int[]{65,20,30,60,40}; } public static void main(String args[]) { Scanner input=new Scanner(System.in); Packet p=new Packet(); int []x=new int [5]; int []y=new int [5]; p.m=100; p.n=5; p.BKnapsack(0, 0, 0, x, y); for(int i=0;i<5;i++) { System.out.println(x[i]); } } public int Bound(int k,int cp,int cw) { int b = cp, c = cw; // 当前收益cp和当前背包重量cw不变(xk=0) for (int i = k + 1; i < n; i++) // 对剩余物品求一般背包问题的解 { c += w[i]; if (c < m) b += p[i]; // 第i个物品能够所有放入 else return b ; } return b; } public void BKnapsack(int k,int cp,int cw,int []x,int []y) { if (cw+w[k]<=m) //左子树,需又一次计算约束函数,上界函数无需计算 { y[k]=1; if (k<n-1) { BKnapsack(k+1,cp+p[k],cw+w[k],x,y); } if (cp+p[k]>fp && k==n-1) //到最底层 { fp=cp+p[k]; //更新最优解下界预计值 for (int j=0;j<=k;j++) x[j]=y[j]; } } if (Bound(k,cp,cw)>=fp) //右子树,需又一次计算上界函数。约束函数无需计算 { y[k]=0; if (k<n-1) //未究竟 BKnapsack(k+1,cp,cw,x,y); if (cp>fp && k==n-1) //到最底层 { fp=cp; //更新最优解下界预计值 for (int j=0;j<=k;j++) x[j]=y[j]; } } } }