优先队列式分支限界法解0-1背包问题

本文介绍了一种使用分支限界法解决0-1背包问题的算法,通过优先队列管理和筛选最优解,实现了对回溯法的有效优化。文章详细解释了算法流程,包括节点上界计算、优先级队列使用、物品类定义和合并排序算法应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曼车斯基

如果内容对你有用,赏杯咖啡吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值