用最小的空间装最大价值的物品是经典的背包问题,而0-1背包是背包问题中最简单的情况,常见的做法有动态规划和回溯法等。
本文用更为容易理解的回溯法来解决该问题。
我们把每个输入的物品都看做一个节点,用标记数组来标记是否使用该物品,于是物品节点之间能够生成一颗二叉树,二叉树节点的左子(1)表示装该物品,右子(0)表示不装该物品。
于是我们可以从二叉树的根节点用深度优先搜索的方法来遍历直到叶子节点,我们对每条路径可以得到一个有效序列来标记是否在背包中装这n个物品(假设n=8,则例如:01100010)可以给出装下3个物品的背包情况。
当然,每次确定装或不装某物品时,我们都要计算当前背包总重,在DFS的同时,如果发现当前背包装的总物品重量过大,则可以直接剪枝,从而大幅度提高程序效率。
注意:通常不只有一种情况可以产生最大价值。
程序如下:
#include<iostream>
using namespace std;
int weight[1001],value[1001];//重量和价值
int use[1001];//标记n个物品是否使用
int c; //背包容量
int max_value=0;//最大价值
int b_count; //记录最大价值的数量
void find_max_value(int n,int max){
int w=0;
for(int i=0;i<n;i++) w+=weight[i]*use[i]; //算总重
if(w>c) return; //如果超重,回溯
else if(n==max){
int v=0;
for(int i=0;i<n;i++) v+=use[i]*value[i]; //算总价值
if(v>max_value) max_value=v;
return ;//找到满足的一个,回溯
}
else{ //不满足,则生产2个子节点(0或1)
use[n]=0;
find_max_value(n+1,max);//不加入当前节点
use[n]=1;
find_max_value(n+1,max); //加入当前节点
}
}
void find_use(int n,int max){
int w=0;
for(int i=0;i<n;i++) w+=weight[i]*use[i]; //算总重
if(w>c) return; //如果超重,回溯
else if(n==max){
int v=0;
for(int i=0;i<n;i++) v+=use[i]*value[i]; //算总价值
if(v==max_value){ //当前是最大值的一种情况
cout<<"case "<<b_count<<":"<<endl;
b_count++;
for(int i=0;i<n;i++){
if(use[i]==1) cout<<(i+1)<<endl;
}
}
return ;//找到满足的一个,回溯
}
else{ //不满足,则生产2个子节点(0或1)
use[n]=1; //先1后0则先输出大的满足的(如5),先0后1则先输出小的(如1 4)
find_use(n+1,max);//不加入当前节点
use[n]=0;
find_use(n+1,max); //加入当前节点
}
}
int main()
{
int n;
char t;
cin>>n;
for(int i=0;i<n;i++) cin>>weight[i]>>t>>value[i];
cin>>c;
find_max_value(0,n);
//cout<<"max value:"<<max_value<<endl;
b_count=1;
find_use(0,n);
system("pause");
return 0;
}
程序进行了两遍搜索,第一遍找出了当前背包的最大价值max_value。第二遍则找出可以符合最大价值max_value的所有物品组合序列。
回溯法解0-1背包问题相对于DP来说,可以更简单理解和入门