Java语言描述:分支限界法之01背包问题

时间:2021-07-04 18:43:30

问题描述:

已知:有一个容量为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+" ");
}
}

程序运行结果:

Java语言描述:分支限界法之01背包问题