優先佇列式分支限界法解0-1揹包問題
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