0-1背包问题:有一个贼在偷窃一家商店时,发现有n件物品,第i件物品价值vi,重wi。他希望带走的东西越值钱越好,但他的背包中至多只能装下C的东西。应该带走哪几样东西,使得装入背包的物品价值最大?这个问题之所以称为0-1背包,是因为每件物品或被带走;或被留下;小偷不能只带走某个物品的一部分或带走同一物品两次。
《算法设计与分析》P133给出的算法并不完整,不利于通过代码来辅助理解该算法的基本思想。书中的代码存在太多槽点,尤其对于double类型直接使用==进行相等比较,可以说是非常的不严谨,不知道代码作者是否实际执行过代码进行验证。
采用java对该算法的改进和完整实现如下,共3个文件。
文件1: BacktrackingBackpack.java , 回溯法算法的实现;
文件2: Element.java 辅助文件 , 表示物品的单位重量的价值;
文件3: MergeSort.java 辅助文件,合并算法对 Element数组进行降序排列。
BacktrackingBackpack.java :
代码中添加了一些辅助调试的代码,通过debug标记可以关闭或打开。
package ch05.book;
import ch05.book.MergeSort;
/**
* This class express the backtracking solution for 0-1 backpack question.
* @author wanyongquan
*
*/
public class BacktrackingBackpack {
final static boolean debug= true;
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.printf("best profit: %f", bestProfit);
}
static double capacity; // the capacity of backpack;
static double[] weight; // the weight array of items;
static double[] profit; // the profit array of items
static double currentWeight, currentProfit;// current weight and profit;
static double bestProfit; // the possible best profit base on current selection;
// selection array
static int[] selectedNode; // just for debug, display selected node information;
public static void backpack(double[] theProfit, double[] theWeight, double theCapacity) {
// initialization
capacity = theCapacity;
int n = theWeight.length;
currentWeight = 0.0;
currentProfit = 0.0;
bestProfit = 0.0;
try {
// initialize the array of virtual profit/weight node.
Element[] priority = new Element[n];
for (int i = 0; i < n; i++) {
priority[i] = new Element(i, theProfit[i] / theWeight[i]);
}
// sort Element array in descend order;
MergeSort.mergeSort(priority);
selectedNode = new int[n];
for (int i=0; i<n; i++) {
//initial selection of each node as 0; 1 means node is selected;
selectedNode[i] = 0;
}
profit = new double[n];
weight = new double[n];
// sort the weight and profit data in descend order;
for (int i = 0; i< n ; i++) {
// update the weight and profit array using the data of Element array in profit/weight descend order;
profit[i] = theProfit[priority[i].id];
weight[i] = theWeight[priority[i].id];
}
// run backtrack from first Element;
backtrack(0);
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
/**
* put feasible element into backpack using backtracking algorithm;
* @param i the index of element node in priority array.
*/
private static void backtrack(int i) {
if( i> profit.length -1) {
// reach leaf node of solution tree, record currentProfit as best profit and return;
bestProfit = currentProfit;
System.out.printf("Find a best profit is %4.1f at node %d .\r\n ", bestProfit, i-1);
return ;
}
// search sub tree
if (currentWeight + weight[i]<= capacity) {
// the left child node is feasible if the weight is NOT exceed capacity
// put left child node in
currentWeight += weight[i];
currentProfit += profit[i];
selectedNode[i] = 1;
displayState (); //System.out.printf("PUT Element %d in backpack, current weight: %f, current profit: %f \r\n", i, currentWeight, currentProfit);
//search the next level nodes of solution tree
backtrack(i+1);
// backtrack
currentWeight -= weight[i];
currentProfit -= profit[i];
selectedNode[i] = 0;
//System.out.printf("Backtrack , REMOVE Element %d from backpack, current weight: %f, current profit: %f \r\n", i, currentWeight, currentProfit);
displayState();
}
else {
// just for debug;
if (debug) {
System.out.printf("Cannot put Element %d in backpack\r\n", i);
}
}
if (debug) {
System.out.printf("check upper bound for right child tree of ");
displayState();
}
// search the right child tree if bound of current node is greater than bestProfit
// otherwise, just ignore right child tree;
if (bound(i+1) > bestProfit) {
backtrack(i+1);
}
else {
// just for debug;
if (debug) {
System.out.printf("The upper bound of right child of node %d is less than current best profit, so cut it\r\n", i);
}
}
}
/**
* return the upper bound at current tree node by put Priority Elements into backpack virtually;
* @param i the index of element node in priority array
* @return
*/
private static double bound(int i) {
int nodeIndex = i ;// just for debug display;
double capacityLeft = capacity - currentWeight; // the remained capacity
double bound = currentProfit; // upper bound of profit;
// add Elements(the whole element) to backpack in priority descend order;
while(i < profit.length && weight[i] <= capacityLeft) {
// the Element i can be put into backpack;
// update capacityleft and bound
capacityLeft -= weight[i];
bound += profit[i];
i++;
}
// if the backpack still has some capacity remained and NO Single element can been add to backpack,
// then add part of the Element to backpack until the backpack is fullfilled.
if (i<profit.length) {
bound += profit[i] * capacityLeft / weight[i];
}
if (debug) {
System.out.printf("the upper bound with node %d not selected : %f\r\n", nodeIndex-1, bound);
}
return bound;
}
/**
* this method display the current state of selection ,current weight, current profit, current best profit;
*/
private static void displayState() {
System.out.print("current selection: ");
for(int k=0; k<selectedNode.length;k++) {
System.out.printf("%d," , selectedNode[k]);
}
System.out.printf("; current Weight: %4.1f, current Profit: %4.1f, bestProfit :%4.1f\r\n", currentWeight, currentProfit, bestProfit);
}
}
Element.java:
package ch05.book;
/**
* This class represent a virtual profit/weight node that be used for process;
* @author wanyongquan
*
*/
public class Element implements Comparable<Element>{
int id;
double valuePerUnitWeight;
public Element(int id, double valuePerUnitWeight) {
this.id = id;
this.valuePerUnitWeight = valuePerUnitWeight;
}
@Override
public int compareTo(Element other) {
// TODO Auto-generated method stub
return Double.compare(this.valuePerUnitWeight , other.valuePerUnitWeight);
}
@Override
public boolean equals(Object other) {
return Math.abs(this.valuePerUnitWeight-((Element)other).valuePerUnitWeight) < 0.0001;
}
}
MergeSort.java
采用合并排序算法对数组进行降序排序的代码,不是本话题重点,暂不提供。
main()方法中给出的测试数据的执行,对应的调试代码如下,可以看到算法的运行过程,对于理解算法很有帮助。
current selection: 1,0,0,0,0,0,0,; current Weight: 10.0, current Profit: 40.0, bestProfit : 0.0
current selection: 1,1,0,0,0,0,0,; current Weight: 40.0, current Profit: 80.0, bestProfit : 0.0
current selection: 1,1,1,0,0,0,0,; current Weight: 65.0, current Profit: 110.0, bestProfit : 0.0
current selection: 1,1,1,1,0,0,0,; current Weight: 115.0, current Profit: 160.0, bestProfit : 0.0
Cannot put Element 4 in backpack
check upper bound for right child tree of current selection: 1,1,1,1,0,0,0,; current Weight: 115.0, current Profit: 160.0, bestProfit : 0.0
the upper bound with node 4 not selected : 177.500000
Cannot put Element 5 in backpack
check upper bound for right child tree of current selection: 1,1,1,1,0,0,0,; current Weight: 115.0, current Profit: 160.0, bestProfit : 0.0
the upper bound with node 5 not selected : 170.000000
current selection: 1,1,1,1,0,0,1,; current Weight: 150.0, current Profit: 170.0, bestProfit : 0.0
Find a best profit is 170.0 at node 6 .
current selection: 1,1,1,1,0,0,0,; current Weight: 115.0, current Profit: 160.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,1,1,1,0,0,0,; current Weight: 115.0, current Profit: 160.0, bestProfit :170.0
the upper bound with node 6 not selected : 160.000000
The upper bound of right child of node 6 is less than current best profit, so cut it
current selection: 1,1,1,0,0,0,0,; current Weight: 65.0, current Profit: 110.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,1,1,0,0,0,0,; current Weight: 65.0, current Profit: 110.0, bestProfit :170.0
the upper bound with node 3 not selected : 167.500000
The upper bound of right child of node 3 is less than current best profit, so cut it
current selection: 1,1,0,0,0,0,0,; current Weight: 40.0, current Profit: 80.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,1,0,0,0,0,0,; current Weight: 40.0, current Profit: 80.0, bestProfit :170.0
the upper bound with node 2 not selected : 175.000000
current selection: 1,1,0,1,0,0,0,; current Weight: 90.0, current Profit: 130.0, bestProfit :170.0
current selection: 1,1,0,1,1,0,0,; current Weight: 130.0, current Profit: 165.0, bestProfit :170.0
Cannot put Element 5 in backpack
check upper bound for right child tree of current selection: 1,1,0,1,1,0,0,; current Weight: 130.0, current Profit: 165.0, bestProfit :170.0
the upper bound with node 5 not selected : 170.714286
Cannot put Element 6 in backpack
check upper bound for right child tree of current selection: 1,1,0,1,1,0,0,; current Weight: 130.0, current Profit: 165.0, bestProfit :170.0
the upper bound with node 6 not selected : 165.000000
The upper bound of right child of node 6 is less than current best profit, so cut it
current selection: 1,1,0,1,0,0,0,; current Weight: 90.0, current Profit: 130.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,1,0,1,0,0,0,; current Weight: 90.0, current Profit: 130.0, bestProfit :170.0
the upper bound with node 4 not selected : 160.000000
The upper bound of right child of node 4 is less than current best profit, so cut it
current selection: 1,1,0,0,0,0,0,; current Weight: 40.0, current Profit: 80.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,1,0,0,0,0,0,; current Weight: 40.0, current Profit: 80.0, bestProfit :170.0
the upper bound with node 3 not selected : 147.857143
The upper bound of right child of node 3 is less than current best profit, so cut it
current selection: 1,0,0,0,0,0,0,; current Weight: 10.0, current Profit: 40.0, bestProfit :170.0
check upper bound for right child tree of current selection: 1,0,0,0,0,0,0,; current Weight: 10.0, current Profit: 40.0, bestProfit :170.0
the upper bound with node 1 not selected : 167.500000
The upper bound of right child of node 1 is less than current best profit, so cut it
current selection: 0,0,0,0,0,0,0,; current Weight: 0.0, current Profit: 0.0, bestProfit :170.0
check upper bound for right child tree of current selection: 0,0,0,0,0,0,0,; current Weight: 0.0, current Profit: 0.0, bestProfit :170.0
the upper bound with node 0 not selected : 157.500000
The upper bound of right child of node 0 is less than current best profit, so cut it
best profit: 170.000000