问题描述:
一位旅行者准备旅行,所以决定挑选一些物品放入背包之中。每一件物品有一个体积和价值,而背包的总体积也是固定的,问该旅行者应该怎样挑选物品,使得总的价值为最大值?注意物品不能分割,即只能要么全部选中,要么不选。
解决方案:
1、动态规划(现在先不管,哪天有空了再说吧~~);
2、回溯法:
使用回溯法最重要的是要确定约束函数和限界函数,只有这样才能确定需要减去哪些枝节,否则解空间太大,根本无法解决。
在此问题中,首先我们可以看出,该问题的约束函数为:
如果当前背包中的物品的总容量是cw,前面的k-1件物品都已经决定好是否要放入包中,那么第k件物品是否放入包中取决于不等式
cw + wk <= M (其中,wk为第k件物品的容量,M为背包的容量)(此即约束条件)
然后我们再寻找限界函数,这个问题比较麻烦,我们可以回忆一下背包问题的贪心算法,即物品按照 物品的价值/物品的体积 来从大到小排列,然后最优解为
(1,1,1…….,1,t,0,0,……),其中0<=t<=1;
因此,我们在确定第k个物品到底要不要放入的时候(在前k-1个物品已经确定的情况下),我们可以考虑我们能够达到的最大的价值,即我们可以通过计算只放入一部分的k物品来计算最大的价值。我们要确保当前选择的路径的最大的价值要大于我们已经选择的路径的价值。这就是该问题的限界条件。通过该条件,可以减去很多的枝条,大大节省运行时间。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
int * pValue = NULL; //物品的价值
int * pWeight = NULL;//物品的重量
int counts = 0; //物品的个数
int *realSolution = NULL; //存放最终的解
int *testSolution = NULL; //存放每次走得路径所得的解
/*计算在前k-1件物品已经做出决策的前提下,考虑可能达到的最大的效益值,返回一个上界,
其中,cp代表背包中当前的价值,cw代表当前的重量,k代表是考虑第几个物品,m代表背包的最大容量
*/
float BoundFound(int cp, int cw, int k, int m)
{
int i = k;
int c = cw;
float b = (float)cp;
while (i<=counts)
{
c += pWeight[i];
if (c<m)
{
b += pValue[i];
}
else
{
return (b+(1-(float)(c-m)/pWeight[i])*(pValue[i]));
}
i++;
}
return b;
}
//m为背包的容量
void BackKnap(int m)
{
int currentWeight = 0;
int currentValue = 0;
int k = 1;
int finalValue = -1;
while(true)
{
while(k<=counts && (currentWeight + pWeight[k] <= m))
{
testSolution[k] = 1;
currentWeight += pWeight[k];
currentValue += pValue[k];
k++;
}
if (k>counts)
{
finalValue = currentValue;
k = counts;
memmove((void *)realSolution, (void *)testSolution, (counts+1) * sizeof(int));
}
else
{
testSolution[k] = 0;
}
//如果发现这样的一条路径走得最好结果也没有我现在的结果好,则果断要求回溯
while (BoundFound(currentValue, currentWeight, k+1, m) <= finalValue)
{
while((testSolution[k] != 1) && (k != 0)) k--;
if (k==0)
{
return ;
}
testSolution[k] = 0;
currentWeight -= pWeight[k];
currentValue -= pValue[k];
}
k++;
}
}
void main()
{
printf("please input the counts of packages:");
scanf("%d", &counts);
printf("please input the volumn of the bag:");
int m = 0;
scanf("%d", &m);
pWeight = (int *)malloc((counts+1) * sizeof(int));
memset((void*)pWeight, 0, sizeof(int)* (counts+1));
for(int i = 1; i <= counts; i++)
{
printf("please input the weight of the %dth package:", i);
scanf("%d", pWeight + i);
}
pValue = (int *)malloc((counts+1) * sizeof(int));
memset((void*)pValue, 0, sizeof(int) * (counts+1));
for(i = 1; i <= counts; i++)
{
printf("please input the value of the %dth package:", i);
scanf("%d", pValue + i);
}
realSolution = (int *)malloc((counts+1) * sizeof(int));
memset((void*)realSolution, 0, sizeof(int) * (counts+1));
testSolution = (int *)malloc((counts+1) * sizeof(int));
memset((void*)testSolution, 0, sizeof(int) * (counts+1));
BackKnap(m);
printf("the best reslut is choosing ");
int maximunValue = 0;
for (i = 1; i<=counts; i++)
{
if (realSolution[i] == 1)
{
maximunValue += pValue[i];
printf("the %dth package ", i);
}
}
printf("\n and the maximun value is %d\n",maximunValue);
}
注意上述代码输入物品的体积和价值的时候,输入的顺序要按照 物品的价值/物品的体积 从大到小排列。
编译环境VC++6.0
出现的主要问题:
1、第64行的while循环开始的时候写成if了,这样的话就不能发挥限界函数的作用了!
2、可以通过一步一步的跟踪调试来熟悉整个背包问题的解空间所构成的树!
3、还是得熟悉回溯法的基本思路啊!