首先初始化总容量capacity = 10、物品总数量number = 4
物品信息为【4,40】、【7、42】、【5、25】、【3、12】,分别为重量weight,价值value。
解决该题目运用到的数据结构有:优先队列、二叉树、存放物品基本信息的数组
这里主要就是构建二叉树,二叉树节点的属性有
weight(当前总容量)
value(当前总价值)
layer(当前层级,用来判断是否为叶子节点)
currentSolution(当前解,算出最大价值后,方便找出拿了哪些物品)
up(上限)
left(左孩子,取当前物品)
right(右孩子,不取当前物品)
树如下
说下各个节点的值是怎么算的
这是根节点,所以初始化weight为0、value为0、up为剩余的物品最有可能能取到的最大价值。所以up = 当前剩余容量 * (value / weight) = 10 * (40 / 4) = 100。layer层级为0。
取第一个物品时。weight = 第一个物品的weight = 4,value同理。layer层级为1,currentSolution为1(1代表取)
up = 当前总价值 + 当前剩余容量 * (第二个物品的value / 第二个物品的weight)= 40 + (10 – 4) * (42 / 7) = 76
不取第一个物品。weight = 0,value = 0。layer层级为1,currentSolution为0(0代表取)
up = 当前剩余容量 * (第二个物品的value / 第二个物品的weight)= 10 * (42 / 7) = 60
取第一个物品、取第二个物品。weight = 4 + 第二个物品weight = 4 + 7 = 11 > 最大容量10。所以该节点不用继续计算和保存。
取第一个物品、不取第二个物品。weight = 原来的weight = 4,value = 原来的value = 40,layer层级为2,currentSolution为10
up = 当前总价值value + 剩余容量 * (第三个物品的value / 第三个物品的weight)= 40 + (10 – 4) * (25 / 5) = 70
取第一个物品、不取第二个物品、取第三个物品。weight = 已有weight + 第三个物品weight = 4 + 5 = 9,value = 已有value + 第三个物品value = 40 + 25 = 65。layer层级为3,currentSolution为101。
up = 当前总价值value + 剩余容量 * (第四个物品的value / 第四个物品的weight)= 65 + (10 – 9) * (12 / 3) = 69
取第一个物品、不取第二个物品、取第三个物品、不取第四个物品。weight = 已有weight = 9,value同理。layer层级为4,currentSolution为1010。
因为layer = 物品总数量number = 4
所以up = 当前总价值value
其它的节点计算方式同理。所以根据以上方式,最后可以计算出一棵二叉树。再对其节点进行判断。
对于左右分支,我们并不知道最终答案是在哪个分支下面,所以两个分支都要判断,不过不能太盲目。应该有一个查找侧重点,因为答案在up值更大的节点下面可能性更大,所以我们有限查找up值较大的节点。这里就需要一个优先队列,把左右节点放入到队列中,up值大的放在前面,优先出队。
最后代码(注释就是思路)。一共有5个类,Backpack、BinaryTree、ItemInfo、Node、Main
ItemInfo.java
package com.aekc.algorithm.backpack;
/**
* 物品基本信息
*/
public class ItemInfo {
/**
* 重量
*/
private int weight;
/**
* 价值
*/
private int value;
public ItemInfo() {}
public ItemInfo(int weight, int value) {
this.weight = weight;
this.value = value;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
Node.java
package com.aekc.algorithm.backpack;
/**
* 分支定界法,节点属性
*/
public class Node extends ItemInfo implements Comparable {
/**
* 层级,用来判断是否为叶子节点
*/
private int layer;
/**
* 当前解,算出最大价值后,方便找出拿了哪些物品
*/
private String currentSolution;
/**
* 上限
*/
private double up;
/**
* 取
*/
private Node left;
/**
* 不取
*/
private Node right;
public Node() {}
public int getLayer() {
return layer;
}
public void setLayer(int layer) {
this.layer = layer;
}
public String getCurrentSolution() {
return currentSolution;
}
public void setCurrentSolution(String currentSolution) {
this.currentSolution = currentSolution;
}
public double getUp() {
return up;
}
public void setUp(double up) {
this.up = up;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
/**
* 优先队列排列方式,根据up值大小来排序
* @param o Node
* @return int
*/
@Override
public int compareTo(Object o) {
Node node = (Node) o;
if(node.getUp() > this.getUp()) {
return 1;
} else if(node.getUp() == this.getUp()) {
return 0;
}
return -1;
}
}
Backpack.java
package com.aekc.algorithm.backpack;
import java.util.*;
/*
4 10
4 40
7 42
5 25
3 12
*/
/**
* 背包
*/
public class Backpack {
/**
* 总容量
*/
private int capacity;
/**
* 总数量
*/
private int number;
public BinaryTree binaryTree;
/**
* 初始化
*/
public void initialization() {
// 创建根节点
Node head = new Node();
head.setWeight(0);
head.setValue(0);
head.setLayer(0);
head.setCurrentSolution("");
// 根据每单位质量价值来从大到小排序
binaryTree.getItemInfoList().sort((o1, o2) -> {
Double a = (double) (o1.getValue() / o1.getWeight());
Double b = (double) (o2.getValue() / o2.getWeight());
return b.compareTo(a);
});
ItemInfo firstNode = binaryTree.getItemInfoList().get(0);
head.setUp(capacity * (firstNode.getValue() * 1.0 / firstNode.getWeight()));
binaryTree.setBackpack(this);
// 创建树
binaryTree.createdBinaryTree(head, 0);
binaryTree.branchBound(head);
}
public void input() {
Scanner scanner = new Scanner(System.in);
// 物品数量
number = scanner.nextInt();
// 背包容量
capacity = scanner.nextInt();
binaryTree.setItemInfoList(new ArrayList<>(number));
for(int i = 0; i < number; i++) {
int weight = scanner.nextInt();
int value = scanner.nextInt();
ItemInfo itemInfo = new ItemInfo(weight, value);
binaryTree.getItemInfoList().add(itemInfo);
}
}
public int getCapacity() {
return capacity;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
}
BinaryTree.java
package com.aekc.algorithm.backpack;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
/**
* 处理节点树
*/
public class BinaryTree {
/**
* 保存所有物品的基本信息,根据每单位质量价值从大到小来排序
*/
private List<ItemInfo> itemInfoList;
/**
* 最优解(假如为:1010,代表第一个拿,第二个不拿,第三个拿,第四个不拿)
*/
private String bestSolution;
/**
* 最优值
*/
private int max;
private Backpack backpack;
/**
* 判断节点是否符合要求
* @param node 树的当前节点
* @param upPriorityQueue 优先队列
*/
private void judgementNode(Node node, Queue<Node> upPriorityQueue) {
if(node == null) {
return;
}
// 当前节点对应的总重量超过背包最大容量 || 当前节点最可能的最大价值up小于已知的价值
// node.getWeight() > backpack.getCapacity()这个判断其实是多余的。。
if(node.getWeight() > backpack.getCapacity() || node.getUp() < max) {
return;
}
// 当前的节点对应的层级是否小于总数量
if(node.getLayer() < backpack.getNumber()) {
upPriorityQueue.add(node);
} else {
// 更新最优价值
max = node.getValue();
// 更新最优解
bestSolution = node.getCurrentSolution();
}
}
/**
* 分支界定
* @param node 根节点
*/
public void branchBound(Node node) {
// 使用优先队列,根据节点的up值,优先权由大到小
Queue<Node> upPriorityQueue = new PriorityQueue<>();
upPriorityQueue.add(node);
while(!upPriorityQueue.isEmpty()) {
node = upPriorityQueue.poll();
// 如果队列中第一个节点的up值小于最优价值,则就没必要继续遍历队列。
if(node.getUp() <= max) {
break;
}
judgementNode(node.getLeft(), upPriorityQueue);
judgementNode(node.getRight(), upPriorityQueue);
}
}
/**
* 创建二叉树
* @param node 当前节点
* @param index 物品List对应下标
*/
public void createdBinaryTree(Node node, int index) {
if(index >= backpack.getNumber()) {
return;
}
// 获取当前物品信息
ItemInfo currentItem = itemInfoList.get(index);
if(node.getLeft() == null) {
Node leftNode = new Node();
// 因为是左节点,代表拿。被拿当前物品价值+已有的物品总价值
leftNode.setValue(currentItem.getValue() + node.getValue());
// 被拿当前物品重量+已有的物品总重量
leftNode.setWeight(currentItem.getWeight() + node.getWeight());
// 设置层级index从0开始,0代表根节点的层级
leftNode.setLayer(index + 1);
// 算出剩余容量
int surplus = backpack.getCapacity() - leftNode.getWeight();
// 这里进行减去枝,如果剩余容量小于0说明当前物品无法放入
if(surplus >= 0) {
// 如果不是最后一层,index在这里,可以理解为层级
if(index + 1 < itemInfoList.size()) {
// 当前物品的下一个物品
ItemInfo nextNode = itemInfoList.get(index + 1);
// 求出剩余物品的最大up值,下一个物品每单位容量最大价值*剩余容量
int up = surplus * (nextNode.getValue() / nextNode.getWeight());
// 是否为根节点
if(index == 0) {
// 根节点只需要获取当前物品的价值+up
leftNode.setUp(up + currentItem.getValue());
} else {
// 非根节点需要获取当前总价值+up
leftNode.setUp(up + leftNode.getValue());
}
} else {
// 最后一层,其实就是最后一个物品,对应最后一层的节点的up就是当前节点的value
leftNode.setUp(node.getValue());
}
// 更新解过程,1代表left,即拿取
leftNode.setCurrentSolution(node.getCurrentSolution() + 1);
node.setLeft(leftNode);
createdBinaryTree(node.getLeft(), index + 1);
}
}
if(node.getRight() == null) {
Node rightNode = new Node();
// 因为是右节点,代表不拿。所以直接设置为已有的物品总价值
rightNode.setValue(node.getValue());
rightNode.setWeight(node.getWeight());
rightNode.setLayer(index + 1);
int surplus = backpack.getCapacity() - rightNode.getWeight();
if(surplus >= 0) {
if(index + 1 < itemInfoList.size()) {
ItemInfo nextNode = itemInfoList.get(index + 1);
int up = surplus * (nextNode.getValue() / nextNode.getWeight());
if(index == 0) {
rightNode.setUp(up);
} else {
rightNode.setUp(up + rightNode.getValue());
}
} else {
rightNode.setUp(node.getValue());
}
rightNode.setCurrentSolution(node.getCurrentSolution() + 0);
node.setRight(rightNode);
createdBinaryTree(node.getRight(), index + 1);
}
}
}
/**
* 遍历树
* @param node 节点
*/
public void preOrderBinaryTree(Node node) {
if(node != null) {
System.out.println(node.toString());
preOrderBinaryTree(node.getLeft());
preOrderBinaryTree(node.getRight());
}
}
public List<ItemInfo> getItemInfoList() {
return itemInfoList;
}
public void setItemInfoList(List<ItemInfo> itemInfoList) {
this.itemInfoList = itemInfoList;
}
public String getBestSolution() {
return bestSolution;
}
public void setBestSolution(String bestSolution) {
this.bestSolution = bestSolution;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public Backpack getBackpack() {
return backpack;
}
public void setBackpack(Backpack backpack) {
this.backpack = backpack;
}
}
Main.java
package com.aekc.algorithm.backpack;
/**
* 运用分支定界法解决背包问题
*/
public class Main {
public static void main(String[] args) {
Backpack backpack = new Backpack();
backpack.binaryTree = new BinaryTree();
backpack.input();
backpack.initialization();
System.out.println("最大价值为:" + backpack.binaryTree.getMax());
String bestSolution = backpack.binaryTree.getBestSolution();
System.out.println("拿取的物品为:");
for(int i = 0; i < bestSolution.length(); i++) {
if(bestSolution.charAt(i) != '0') {
ItemInfo itemInfo = backpack.binaryTree.getItemInfoList().get(i);
System.out.println("[" + itemInfo.getWeight() + " " + itemInfo.getValue() + "]");
}
}
}
}
测试
4 10
4 40
7 42
5 25
3 12
最大价值为:65
拿取的物品为:
[4 40]
[5 25]