1. 程式人生 > >Java語言描述:分支限界法之01揹包問題

Java語言描述:分支限界法之01揹包問題

問題描述:

已知:有一個容量為V的揹包和N件物品,第i件物品的重量是weight[i],收益是value[i]。

限制:每種物品只有一件,可以選擇放或者不放

問題:在不超過揹包容量的情況下,最多能獲得多少價值或收益。

/* 
 * 本程式碼實現了運用優先佇列式分支限界法解決了01揹包問題。解決問題的大致思想和前2篇部落格的回溯法
 * 大致相同,都屬於搜尋演算法,不過實現方式上略有不同。對於子集樹問題,利用優先佇列式分支限界法
 * 的關鍵之一就是要確定優先順序的設定。在本程式碼中,優先順序設定為每個節點處能夠取到的價值上界。  
 * 對於尋找剩餘物品的最高價值上界,按照揹包中剩餘空間依次取剩下的物品,當空間不足以取下一個 
 * 物品時,則將下一個物品的單位重量價值折算到現在的剩餘空間中去計算理想最高上界。計算出某個
 * 節點的上界後,比較上界和已經找到的最大值之間的關係。
 * 當然,首先要揹包中的物品按照單位重量價值進行排序,方便於後面右子樹的剪枝操作。
 * 在本程式碼中,省略了該排序過程,在初始化物品的重量和價值時,已經按照單位重量的價值排好了序。
 * 
 * 剪枝函式:
 * 對於向左搜尋,可以通過是否超過揹包容量和該節點價值上界能否超過最大值進行剪枝。
 * 對於向右搜尋,則可以用其父節點的上界減去本層物品的價值,即可得到右邊節點的上界。 
 * */
package BranchAndBounding;

import java.util.PriorityQueue;
//定義節點中的引數以及優先順序設定的物件
class thingNode implements Comparable<thingNode>{
	int weight;//該節點目前揹包中的重量
	double value;//該節點目前揹包中的總價值
	double upprofit;//該節點能夠達到的價值上界
	int Left; 	//該節點是否屬於左節點(用於最終構造最優解)
	int level;  //該節點是第幾個物品的選擇
	thingNode father; //該節點的父節點
	public int compareTo(thingNode node){
		if(this.upprofit<node.upprofit)
			return 1;
		else if(this.upprofit == node.upprofit)
			return 0;
		else
			return -1;
	}
}
public class bag01 {
	int n = 5;
	int capacity = 10;
	int[] weight = {2,6,4,1,5};
	double[] value = {6,9,6,1,4};
	int maxValue = 0;
	int[] bestWay = new int[n];
	public void getMaxValue(){
		PriorityQueue<thingNode> pq = new PriorityQueue<thingNode>();
		//構造一個初始化節點,屬於-1層
		thingNode initial = new thingNode();
		initial.level = -1;
		initial.upprofit = 26;
		pq.add(initial);
		while(!pq.isEmpty()){
			thingNode fatherNode = pq.poll();
			//當已經搜尋到葉子節點時
			if(fatherNode.level == n-1){
				if(fatherNode.value > maxValue){
					maxValue = (int)fatherNode.value;
					for(int i=n-1;i>=0;i--){
						bestWay[i] = fatherNode.Left;
						fatherNode = fatherNode.father;
					}
				}	
			}
			else{
				//先統計其左節點資訊,判斷是否加入佇列。
				if(weight[fatherNode.level+1]+fatherNode.weight <= capacity){
					thingNode newNode = new thingNode();
					newNode.level = fatherNode.level+1;
					newNode.value = fatherNode.value + value[fatherNode.level+1];
					newNode.weight = weight[fatherNode.level+1]+fatherNode.weight;
					newNode.upprofit = Bound(newNode);
					newNode.father = fatherNode;
					newNode.Left = 1;
					if(newNode.upprofit > maxValue)
						pq.add(newNode);
				}
				//向右節點搜尋,其能夠取到的價值上界通過父親節點的上界減去本層物品的價值。
				if((fatherNode.upprofit - value[fatherNode.level+1])> maxValue){
					thingNode newNode2 = new thingNode();
					newNode2.level = fatherNode.level+1;
					newNode2.value = fatherNode.value;
					newNode2.weight = fatherNode.weight;
					newNode2.father = fatherNode;
					newNode2.upprofit = fatherNode.upprofit - value[fatherNode.level+1];
					newNode2.Left = 0;
					pq.add(newNode2);
				}
				
			}
		}
	}
	//用於計算該節點的最高價值上界
	public double Bound(thingNode no){
		double maxLeft = no.value;
		int leftWeight = capacity - no.weight;
		int templevel = no.level;
		//盡力依照單位重量價值次序裝剩餘的物品
		while(templevel <= n-1 && leftWeight > weight[templevel] ){
			leftWeight -= weight[templevel];
			maxLeft += value[templevel];
			templevel++;
		}
		//不能裝時,用下一個物品的單位重量價值折算到剩餘空間。
		if( templevel <= n-1){
			maxLeft += value[templevel]/weight[templevel]*leftWeight;
		}
		return maxLeft;
	}
	public static void main(String[] args){
		bag01 b = new bag01();
		b.getMaxValue();
		System.out.println("該揹包能夠取到的最大價值為:"+b.maxValue);
		System.out.println("取出的方法為:");
		for(int i : b.bestWay)
			System.out.print(i+"  ");
	}
}

程式執行結果: