运用分支定界法(分支限界法)解决01背包问题

  • Post author:
  • Post category:其他


首先初始化总容量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]



版权声明:本文为XlxfyzsFdblj原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。