【算法】用“0-1背包问题”的观点来解搜狐2012年的那道题
昨天终于实现了“0/1背包问题”的算法,并且将搜狐2012年的那道题当做“0/1”背包问题来解,也获得了成功。在这个过程中,我发现了个问题:原来“0/1背包问题”的算法也是通过试探和回溯来实现的(因为我的能力现在有限,不能将算法的整个过程看到底,所以还没能确定算法里是否有回溯,我偏向于有)。那本《数据结构》中提到:“回溯法也是设计递归过程的一种重要方法,它的求解过程实质上是一个先序遍历一棵‘状态树’的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中,但如果认识到这点,很多问题的递归过程设计也就迎刃而解了”。
但是,“0/1背包问题”与“求含N个元素的集合的幂集”有个重大的不同:“0/1背包问题”是有条件限制的。这个限制条件是:背包的容量,或者成为承重量最大不超过M。这样当试探过程中出现的状态和问题的限制条件矛盾时,便不再继续试探下去。
“0/1背包问题”的完整描述是:有一个背包,承重量为W;有N种物品,每种物品有且仅有1件,物品i的重量为wi,价格为pi;要求将物品放入包中,在包能承受的条件下,使包中的物品价值达到最大。很显然,物品i要么被放入包中,要么不被放入包中,所以称该问题为“0/1背包问题”。如果大家觉得我描述得根本不清楚,大家可以自己到网上查,我是通过维基百科了解“0/1”背包问题的,网址如下:http://zh.wikipedia.org/zh/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98。
怎样用“0/1背包问题”的算法来解搜狐的这道题呢?很多人都提出了自己的想法,其中,chinaunix论坛上的网友koolcoy(娶妻求贤)也给出了自己的算法,虽然概括,但很简单,很清晰。他提出的算法是:
第一步:找出和比M小的一组数,并求出这个和能取到的最大值;
第二步:找出和比M大的一组数,并求出这个和能取到的最小值;
第三步:比较第一步和第二步计算出的结果。
其中,第二步可以采用如下的方法来解:求出M相应于这N个数的总和sumN的补数complementaryM,找出和比complementaryM小的一组数,并使这组数的和complementaryM取得最大值,那么这N个数中,除去这组数,剩下的那组数的和便比M大,同时最小。
还有一个问题,就是这组数是作为物品的价值呢,还是重量呢?因为M是个界限,所以M相当于包的承重量,而这组数中每个元素的类型都与M相同,所以这组数可以作为物品的重量。那么物品的价格这个角色有谁来担当呢?因为“0/1背包问题”求的是价值的最大值,本题求的是最接近M,所以价值还得由这组数来担当,即该问题中物品的价值和它的重量在数量上是相等的(单位当然不等了~~)。
说了这么多,可能还是没有说明白,还是那句话,大家可以到网上自己查找~~。
源代码如下,请指正:“
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
#define Debug_Enable 0
#define Solve_TheQuestion 1
typedef struct {
double w;
double v;
} Item;
/*
全局变量默认值总是0.
*/
const int MaxCnt = 100; // 本程序最多处理100种(1个/种)物品
Item items[MaxCnt];
int cnt = 0; // 物品数量
void FindMax(int i, double curW, double tolV, const double limitW, double &maxV, int *
workVector, int * option)
{
int k = 0;
if (curW+items[i].w <= limitW) // 包含物品i是可接受的
{
workVector[i] = 1; // 将物品i包含在当前方案中
if (i < cnt-1)
{
FindMax(i+1, curW+items[i].w, tolV, limitW, maxV, workVector, option);
}
else
{
// 又一个完整方案, 且比前面的方案好:
for (k=0; k<cnt; ++k)
{
option[k] = workVector[k];
}
maxV = tolV;
}
workVector[i] = 0; // 恢复物品i不包含状态
/*
虽然像算法6.14, 但此算法应该没有回溯吧~~, SLL, 2011-11-15
此算法没有回溯? SLL, 2011-11-16
*/
}
if (tolV-items[i].v > maxV) // 不包含物品i仅是可考虑的
{
if (i < cnt-1)
{
FindMax(i+1, curW, tolV-items[i].v, limitW, maxV, workVector, option);
}
else
{
for (k=0; k<cnt; ++k)
{
option[k] = workVector[k];
}
maxV = tolV - items[i].v;
}
}
}
int main()
{
double totalW = 0.0;
double totalV = 0.0; // 本方案可能达到的总价值
double maxV1 = 0.0; // 本方案所达到的总价值
double maxV2 = 0.0;
double limitW = 0.0; // 包的最大容量
int k = 0; // 循环变量
double w = 0.0;
double v = 0.0;
cout << "Input the total number(<=100): ";
cin >> cnt;
while (cnt > 100)
{
cout << "The total number is too large! Please<=100: ";
cin >> cnt;
}
int * pOption1 = new int[cnt]; // 以0/1序列的形式, 标记方案中选择的与舍弃的物品
if (!pOption1)
{
cout << "Memory Error!" << endl;
exit(1);
}
int * pOption2 = new int[cnt];
if (!pOption2)
{
cout << "Memory Error!" << endl;
exit(1);
}
int * pWorkVector = new int[cnt];
if (!pWorkVector)
{
cout << "Memory Error!" << endl;
exit(1);
}
memset(pOption1, 0, sizeof(int) * cnt);
memset(pOption2, 0, sizeof(int) * cnt);
memset(pWorkVector, 0, sizeof(int) * cnt);
cout << "Input the value of the elements: ";
for (k=0; k<cnt; ++k)
{
cin >> w;
v = w;
items[k].w = w;
items[k].v = v;
totalW += items[k].w;
totalV += items[k].v;
}
cout << "Input the target: ";
cin >> limitW;
FindMax(0, 0.0, totalV, limitW, maxV1, pWorkVector, pOption1);
int complementaryLimW = totalW - limitW;
FindMax(0, 0.0, totalV, complementaryLimW, maxV2, pWorkVector, pOption2);
int complementaryMinV = totalV - maxV2;
if (abs(maxV1-limitW) < abs(complementaryLimW-limitW))
{
cout << "the nearest value with " << limitW << " is: " << maxV1 << '\n'
<< "the elements are: ";
for (k=0; k<cnt; ++k)
{
if (pOption1[k])
{
cout << items[k].w << ' ';
}
}
cout << endl;
}
else
{
cout << "the nearest value with " << limitW << " is: " << complementaryMinV << '\n'
<< "the elements are: ";
for (k=0; k<cnt; ++k)
{
if (!pOption2[k])
{
cout << items[k].w << ' ';
}
}
cout << endl;
}
if (pWorkVector)
{
delete [] pWorkVector;
pWorkVector = NULL;
}
if (pOption2)
{
delete [] pOption2;
pOption2 = NULL;
}
if (pOption1)
{
delete [] pOption1;
pOption1 = NULL;
}
return 0;
}
”。
浙公网安备 33010602011771号