1. 程式人生 > >優先佇列式分支限界法解0-1揹包問題

優先佇列式分支限界法解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