实验原理
分支限界法采用的是广度优先搜索,而优先队列采用的是队列里最优的出队,这里可以使用最大堆来实现活接垫优先队列,最大堆以活节点的界值作为优先级。
实验步骤
左子树的解的上界与父节点相同,不用计算。右子树的解的界值是:将剩余物品以其单位重量价值排序,然后依次装入物品,直到装不下时,再装入物品的一部分来装满背包,即采用贪心算法算出最大值,由此得到右子树中解的上界(虽然不是一个可行解,但可以作为最优解的上界)。
关键代码
double bound(int i) {
double cleft = c - cw;
double b = cp;
while (i <= n && w[i] <= cleft) {
cleft = cleft - w[i];
b = b + p[i];
i++;
}
// 装填剩余容量装满背包
if (i <= n)
b = b + p[i] / w[i] * cleft;
return b;
}
// addLiveNode将一个新的活结点插入到子集树和优先队列中
void addLiveNode(double up, double pp, double ww, int lev, BBnode* par, bool ch) {
// 将一个新的活结点插入到子集树和最大堆中
BBnode* b = new
BBnode(par, ch);
HeapNode node = HeapNode(b, up, pp, ww, lev);
heap->put(node);
}
double MaxKnapsack(){
// 优先队列式分支限界法,返回最大价值,bestx返回最优解
BBnode* enode = new
BBnode();
int i = 1;
double bestp = 0; // 当前最优值
double up = bound(1); // 当前上界
while (i != n + 1) {// 非叶子结点
// 检查当前扩展结点的左儿子子结点
double wt = cw + w[i];
if (wt <= c) {
if (cp + p[i] > bestp)
bestp = cp + p[i];
addLiveNode(up, cp + p[i], cw + w[i], i + 1, enode, true);
}
up = bound(i + 1);
if (up >= bestp)
addLiveNode(up, cp, cw, i + 1, enode, false);
HeapNode node = heap->removeMax();
enode = node.liveNode;
cw = node.weight;
cp = node.profit;
up = node.upperProfit;
i = node.level;
}
for (int j = n; j > 0; j--) {
bestX[j] = (enode->leftChild) ? 1 : 0;
enode = enode->parent;
}
return cp;
}
double knapsack(double* pp, double* ww, double cc, int* xx) {
// 返回最大值,bestX返回最优解
c = cc;
// n = sizeof(pp) / sizeof(double);
// 定义以单位重量价值排序的物品数组
Element* q = new
Element[n];
double ws = 0.0;
double ps = 0.0;
for (int i = 0; i < n; i++) {
q[i] = Element(i + 1, pp[i + 1] / ww[i + 1]);
ps = ps + pp[i + 1];
ws = ws + ww[i + 1];
}
if (ws <= c) {
return ps;
}
p = new double[n + 1];
w = new double[n + 1];
for (int i = 0; i < n; i++){
p[i + 1] = pp[q[i].id];
w[i + 1] = ww[q[i].id];
}
cw = 0.0;
cp = 0.0;
bestX = new int[n + 1];
heap = new MaxHeap(n);
double bestp = MaxKnapsack();
for (int j = 0; j < n; j++)
xx[q[j].id] = bestX[j + 1];
return bestp;
}