在前面的利用贪心算法求解背包问题的时候我们能得到理论的最优解,在回溯法涉及到对于子树的剪枝的时候可以利用贪心算法的得到的最优解(背包问题的最优解是01背包问题解的上界)作为我们剪枝的条件。
我们定义子集树的左节点代表将当前物品加入背包,右节点代表不将当前物品加入背包。
进入左节点的条件(即加入物品到背包):
当前物品的重量小于背包的剩余可容纳的重量。
进入右节点的条件(即不把当前物品加入到背包):
在不加入当前物品的情况下,剩余节点的最大价值上界(利用贪心算法求解)加上当前的价值小于我们求解的当前的最大价值,我们就无需考虑其右节点的情况。
#include <stdio.h>
int c=7; //背包的总容量
int n=4; //物品的总件数
//默认按照单位价值从大到小排序
int w[10]={0,1,2,3,5}; //物品的质量 从 1 开始存储
int v[10]={0,5,8,9,10}; //物品的价值 从 1 开始存储
// 单位价值为: 5 4 3 2
int cw=0; //当前重量
int cv=0; //当前价值
int bestv=0; //当前最优价值
//利用贪心算法取出最优解的上界限
float bound(int i){ //传入节点 i
//保存剩余的背包容量
float cleft=c-cw; //总容量 减去 当前已经装了的重量
float tempv=cv; //保存当前的价值
int j;
for( j=i;j<=n;j++){
if(w[j]<cleft){
tempv+=v[j];
cleft-=w[j];
} else{
tempv+=v[j]*(cleft/w[j]);
break;
}
}
return tempv;
}
void knip(int i){
//i 表示当前是哪一件物品 从 1 开始
if(i>n){
bestv=cv;
return;
}
// 当前位于节点i 判断是否可以进入左节点
if(cw+w[i]<=c){ //当前物品i的质量小于 背包剩余质量 可以被加入到背包
//加入背包 改变当前的价值和重量
cw+=w[i];
cv+=v[i];
knip(i+1); // 进入下一个节点
//回溯到前一个状态
cw-=w[i];
cv-=v[i];
}
// 当前位于节点i 判断是否可以进入右节点 即当前物品不加入背包
if(bound(i+1)>bestv){
//后续节点的价值上界大于当前最优价值,则可以进入右结点
//否则最优的都小于或等于当前的 就没必要再进入右节点
knip(i+1);
//进入右节点 因为不加入到背包 故 当前的价值 重量 都不发生改变
}
}
int main(void){
knip(1);
printf("最大价值为:%d\n",bestv);
return 0;
}