回溯法又称试探法。回溯法的基本做法是深度优先搜索,是一种组织得井井有条的、能避免不必要重复搜索的穷举式搜索算法。
回溯算法的基本思想:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
题目:
给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.
输入格式:
输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
以后N行每行两个数Wi和Vi,表示物品的重量和价值
输出格式:
输出1行,包含一个整数,表示最大价值。输出1行,包含一个整数,表示最大价值。
样例输入:
3 5
2 3
3 5
4 7
样例输出:
8
数据规模和约定
1<=N<=200,M<=5000.
本题采用回溯法进行求解,回溯法的特点就是一直走到底,如果遇见限制条件此路不通,就再换另一条路试试。在本题中,可以将每个物品的选择方案构成一颗二叉树,左子树为选择,右子树为不选择,设置一个变量记录最大价值,添加一些修剪条件修剪该二叉树,通过递归遍历更新该变量,当背包再也装不下任何物品时,该变量达到最优。
伪代码:
①设整形变量r,代表此时背包剩余的空间;maxv,代表最大价值;cv,代表此时的价值;rs,剩余物品的总重量;rv,剩余物品的总价值;k,代表选择到第几个物品。
②设置修剪条件
1: r > = 0 ? 判断背包是否已经装满
2: r > rs && cv + rv > maxv ? 判断如果可以将剩下所有的物品放入背包是否可以达到最优解
3: r < rs && r > 0 ? 如果不能全部装下并且剩余容量可以放下该物品,分别选择放和不放两种情况。
③递归调用,每次递归更新 k , r , cv , rs , rv 的值,直到 r < 0 结束
④输出最大价值 maxv,结束。
代码:
package 回溯法_01背包问题;
import java.util.Scanner;
public class package0_1 {
int n;//物品的个数
int c;//背包的容量
int k;//记录此时达到第几层(即对哪一个物品进行判断)
int r;//背包剩余容量
int cw=0;//到目前为止的重量
int cv=0;//到目前为止的价值
int rs;//此时背包的剩余空间
int rv;//此时背包的剩余
int maxv=0;//最大价值
int w[];//各物品的重量
int v[];//各物品的价值
int x[];//记录每个物品的选择
int x0[];//记录物品最终的选择
//进行初始化,传递参数
public package0_1 (int n,int c,int rs,int rv, int w[],int v[],int x[],int x0[] ) {
this.n = n;
this.c = c;
this.rs = rs;
this.rv = rv;
this.w =w;
this.v = v;
this.x = x;
this.x0 = x0;
}
//将x[]赋值给x0[]
public void FuZhi (int m,int l,int z[],int z0[])
{
for(int i=m;i<=l;i++)
{
z0[i]=z[i];
}
}
//给x0[]赋值0或1
public void FuZhi1 (int m,int l,int z0[],int num)
{
for(int i=m;i<=l;i++)
{
z0[i]=num;
}
}
public void HuiSu(int k,int r,int cv, int rs,int rv) {
//更新最优选择
if(r>=0 && cv > maxv) {
maxv = cv;
FuZhi(1, k, x, x0);
FuZhi1(k+1, n, x0, 0);
}
//支减
if(r>=rs)// 背包剩余的容量大于等于剩余物体的总重量
{
if(cv+rv>maxv) //当前价值加上剩余物体的价值大于此时的最优价值
{
maxv = cv+rv;
FuZhi(1, k, x, x0);
FuZhi1(k+1, n, x0, 1);
}
}
else {
if(r>0 && cv+rv>maxv) // 可以放并且当前价值加上剩余物体的价值大于此时的最优价值
{
// 将下一个物品从未选择物品队列中排除
rv = rv-v[k+1];
rs = rs-w[k+1];
//不放该物品,搜索左子树
x[k+1]=0;
HuiSu(k+1, r, cv, rs, rv);
//放该物品,搜索右子树
x[k+1]=1;
HuiSu(k+1, r-w[k+1], cv+v[k+1], rs, rv);
}
}
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int c = input.nextInt();
int [] w= new int[n+1];
int [] v= new int[n+1];
int [] x= new int[n+1];
int [] x0= new int[n+1];
int rs=0;
int rv=0;
for(int i=1;i<=n;i++)
{
w[i] = input.nextInt();
v[i] = input.nextInt();
}
for(int i=1;i<=n;i++)
{
rs=rs+w[i];
}
for(int i=1;i<=n;i++)
{
rv=rv+v[i];
}
package0_1 pag = new package0_1(n, c, rs, rv, w, v, x, x0);
pag.HuiSu(0, c, 0, rs, rv);
System.out.print(pag.maxv);
}
}
结果:
解决该问题的关键是利用背包剩余容量 r 来判断该物品是否可以加入背包。在本题中我觉得很巧妙的一点就是判断是否可以将剩下的所有物品放入背包,大大减少了二叉树的分支,修剪起来比较简单。
记录整理一些学习中的问题,如果有不恰当和错误的地方,欢迎批评指正~