0-1 背包问题的描述在上一篇《回溯法解0-1背包问题》中已有说明。
现在采用优先队列式分支限界法来求解;
1. 优先队列中节点i的优先级由该节点的上界函数bound计算出的值upperprofit给出。该上界函数与回溯法中计算方法一致。
子集树中以i结点为跟的子树的任一节点的上界不会超过i节点的上界。
《算法设计与分析》P171给出的算法存在几个老问题:
1)数组索引不采用从0开始的编程惯例;
2)浮点数直接用==进行相等判断;
3) 书中代码使用的MaxHeap和MergeSort代码未给出;
4)未采用Java面向对象编程的特点;比如书中的Element对象接受Profit[i]/Weight[i]的计算值作为成员变量,而不是直接把Profit[i]和Weight[i]作为成员变量。 在后续需要访问profit时,只好使用pp[q[i].id] 这样晦涩的语句。 profit和weight直接作为Element的成员变量,使得Element表示一个物品的概念很清晰,需要访问profit时,直接使用queue[i].profit即可。
我只对书中代码不便于理解的部分进行了改进,并没有完全按照面向对象方法改造算法;
改进后文件共3个:
1. BBBackpack.java 分支限界法主算法;
2. Element.java 代表物品;
3. MergeSort.java 合并排序算法,对Element数组实现按照profit/weight从大到小排序;
1. BBBackpack.java
package ch06.book;
import java.util.PriorityQueue;
public class BBBackpack {
static double capacity ; // the capacity of backpack
static double[] weight; // the weight array of goods
static double[] profit; // the profit array of goods
static double currentWeight; // current weight of goods in backpack
static double currentProfit; // current profit of goods in backpack
static int[] bestSolution; // the node path of best solution in solution space tree
static PriorityQueue<HeapNode> heap ; // the Max Heap of live nodes;
public static void main(String[] args) {
// TODO Auto-generated method stub
double[] weight = {35, 30, 60, 50, 40, 10, 25};
double[] profit = {10, 40, 30, 50, 35, 40, 30};
backpack(profit, weight, 150);
System.out.print("the best solution: ");
for(int i=0; i<bestSolution.length; i++) {
System.out.printf("\t[%d]: %d", i, bestSolution[i]);
}
}
/**
* This method pre-process the input data by sort the goods with Profit/Weight in descend order,
* then call method branchBoundBackpack() to complete the Priority Queue Branch Bound Search.
* @param theProfit the array of goods profit
* @param theWeight the array of goods weight
* @param theCapacity the capacity of backpack
* @param thePackingState the array of packing state of goods
* @return
*/
private static double backpack(double[] theProfit, double[] theWeight, double theCapacity) {
capacity = theCapacity;
int goodsNumber = theProfit.length;
//***********step 1: pre-process**********************************************
// calculate the Profit/Weight for each goods and store in array of Element. *
//****************************************************************************
Element[] queue = new Element[goodsNumber];
for (int i=0; i< goodsNumber; i++) {
// calculate the Profit/Weight and generate a Element object, put it in array;
queue[i] = new Element(i, theProfit[i] , theWeight[i]);
}
//************Step2: sort *****************************************************
// sort the array of Elements in Descend order using MergeSort; *
//*****************************************************************************
MergeSort.mergeSort(queue);
// initialize the Profit and Weight array with the SORTED data;
profit = new double[goodsNumber];
weight = new double[goodsNumber];
for (int i=0; i<goodsNumber; i++) {
profit[i] = queue[i].profit;
weight[i] = queue[i].weight;
}
currentWeight = 0.0;
currentProfit = 0.0;
bestSolution = new int[goodsNumber];
heap = new PriorityQueue<HeapNode>();
//*********Step 3: call method branchBoundBackpack()********************
double maxProfit = branchBoundBackpack();
return maxProfit;
}
/**
* This method go through the goods array and return the max profit and best path of selection;
* @return
*/
private static double branchBoundBackpack() {
// initialization
BBNode expandNode = null; // working expand node
int i =0;
double bestProfit =0.0; // working best profit;
double upperProfit = bound(0); // working upper bound profit ;
// search the subset tree while current node is not a leaf node
while (i < weight.length) {
// check left child node;
double targetWeight = currentWeight + weight[i];
if (targetWeight <= capacity) {
// adding left child without exceed the capacity, makes left child be a feasible node;
// check if new profit is better than current best profit,
// then update best profit if true;
double targetProfit = currentProfit + profit[i];
if (targetProfit > bestProfit) {
bestProfit = targetProfit;
}
// add the left child in MaxHeap
addLiveNode(upperProfit, targetProfit, currentWeight + weight[i], i+1, expandNode, true);
}
// update upper boundary
upperProfit = bound(i + 1);
// check right child node;
if (upperProfit >= bestProfit) {
addLiveNode(upperProfit, currentProfit, currentWeight , i+1, expandNode, false);
}
// pop and process next expand node from MaxHeap
HeapNode node = heap.poll();
expandNode = node.liveNode;
currentWeight = node.weight;
currentProfit = node.profit;
upperProfit = node.upperProfit;
i = node.level;
}
// build the best solution
for(int j= weight.length -1; j>=0; j--) {
bestSolution[j]= expandNode.isLeftChild?1:0;
expandNode = expandNode.parent;
}
System.out.printf(" Current best Proft : %7.2f\r\n", bestProfit);
return currentProfit;
}
/*
* calculate the bound for node i, NOT do real packing ;
* prerequisite: the goods is sorted by profit per weight in ascend order;
*/
private static double bound(int i) {
// remain capacity
double capacityRemains = capacity - currentWeight;
// upper bound of profit
double boundary = currentProfit;
// calculate the upper bound by adding feasible goods with this one to backpack
while(i < weight.length && weight[i] <= capacityRemains) {
capacityRemains -= weight[i];
boundary += profit[i];
i++;
}
if (i< weight.length) {
// if the backpack is not fulfilled when there is no feasible goods,
// then adding parts of goods with highest value per weight until the capacity is 0;
boundary += profit[i]/ weight[i] * capacityRemains;
}
return boundary;
}
private static void addLiveNode(double upperProfit, double profit, double weight, int level, BBNode parent, boolean isLeft) {
// add a tree node into MaxHeap
BBNode node = new BBNode(parent, isLeft);
HeapNode heapNode = new HeapNode(node, upperProfit, profit ,weight, level);
heap.add(heapNode);
}
/**
* This internal class represents a node of solution space tree;
*/
static class BBNode{
BBNode parent;
boolean isLeftChild;
public BBNode(BBNode parent, boolean isLeft) {
this.parent = parent;
this.isLeftChild = isLeft;
}
}
static class HeapNode implements Comparable<HeapNode>{
BBNode liveNode ; // live node related to this heap node;
double profit; // the profit of this node;
double upperProfit; // the upper bound of profit for this node;
double weight; // weight of this node;
int level; // the level of node in subset tree
public HeapNode(BBNode treenode, double upperProfit, double profit, double weight, int level) {
this.liveNode = treenode;
this.upperProfit = upperProfit;
this.profit = profit;
this.weight = weight;
this.level = level;
}
@Override
public int compareTo(HeapNode obj) {
// Note: we need a MaxHeap for algorithm, be careful of the order of two comparable item;
return (Double.compare(obj.upperProfit, this.upperProfit));
}
}
}
2. Element.java
package ch06.book;
/**
* This internal class represent a goods,which has weight and price ;
* @author wanyongquan
*
*/
public class Element implements Comparable<Element>{
int id; // the id number;
double valuePerUnitWeight; // good value of unit weight;
double profit;
double weight;
public Element(int id, double profit, double weight) {
this.id = id;
this.profit = profit;
this.weight = weight;
this.valuePerUnitWeight = profit/weight;
}
@Override
public int compareTo(Element obj) {
// compare the valuePerWeight of two object;
if (Math.abs(this.valuePerUnitWeight - obj.valuePerUnitWeight) <= 0.00001)
return 0;
else if (this.valuePerUnitWeight - obj.valuePerUnitWeight >0.0001)
return 1;
else
return -1;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Element other = (Element)obj;
return Math.abs(this.valuePerUnitWeight - other.valuePerUnitWeight) < 0.00001;
}
}
3. MergeSort.java
采用合并排序算法对数组进行降序排序的代码,不是本话题重点,暂不提供。
main函数中采用回溯法中相同的测试数据,得到的运行结果如下:
Current best Proft : 170.00
the best solution: [0]: 1 [1]: 1 [2]: 1 [3]: 1 [4]: 0 [5]: 0 [6]: 1