分支限界_01背包问题_Java实现

转载请注明出处:http://blog.csdn.net/ljmingcom304/article/details/50324007
本文出自:【梁敬明的博客】

1.分支限界算法  

  分支限界法类似于回溯法,回溯法的求解目标是找出解空间中满足约束条件的所有解,而分支限界法则是找出满足约束条件的一个最优解。由于求解目标不同,对解空间的搜索方式也不相同,回溯法以深度优先的方式搜索解空间,分支限界法以广度优先或最小耗费优先的方式搜索解空间。 
  常见的分支限界法有队列式(FIFO)分支限界法和优先队列式分支限界法。队列式分支限界法按照队列先进先出的原则选取下一个节点为扩展节点。优先队列式分支限界法按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。

《分支限界_01背包问题_Java实现》    

  队列式分支限界法搜索路线为:【A】→【B】→【C】→【D】(舍去)→【E】→【F】→【G】→【J】(舍去)→【K】→【L】(舍去)→【M】→【N】→【O】。

  优先队列式分支限界法搜索路线为:【A】→【B】→【C】→【D】(舍去)→【E】→【J】(舍去)→【K】→【F】→【G】→【L】(舍去)→【M】→【N】→【O】。

2.01背包问题

  一个旅行者有一个最多能装m公斤的背包,现在有n中物品,每件的重量分别是W1、W2、……、Wn,每件物品的价值分别为C1、C2、……、Cn, 需要将物品放入背包中,要怎么样放才能保证背包中物品的总价值最大?

3.算法分析  

  本次采用队列式分支限界法来解决01背包问题,拿到一个物品k存在两个节点:物品放入背包中或物品不放入背包中,根据约束条件舍去无效情况。当前物品k放入背包中时,获取当前最大总价值,下一个物品k+1也存在放入背包和不放入背包中两个节点。当物品k不放入背包时,上个节点的最总价值为当前节点的最总价值,下一个物品k+1仍然存在放入背包和不放入背包两个节点。对于物品k+1以此类推,最终根据不同的放入情况选择一个最优解作为可以放入背包物品的最大总价值。
  创建一个物品对象,封装重量、价值、单位重量价值三种属性。

public class Knapsack implements Comparable<Knapsack> {
    /** 物品重量 */
    private int weight;
    /** 物品价值 */
    private int value;
    /** 单位重量价值 */
    private int unitValue;

    public Knapsack(int weight, int value) {
        this.weight = weight;
        this.value = value;
        this.unitValue = (weight == 0) ? 0 : value / weight;
    }

    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;
    }

    public int getUnitValue() {
        return unitValue;
    }

    @Override
    public int compareTo(Knapsack snapsack) {
        int value = snapsack.unitValue;
        if (unitValue > value)
            return 1;
        if (unitValue < value)
            return -1;
        return 0;
    }

}

  按照分支限界算法将物品放入背包中。

public class FZXJProblem {

    // 准备放入背包中的物品
    private Knapsack[] bags;
    // 背包的总承重
    private int totalWeight;
    // 给定的物品数
    private int n;
    // 物品放入背包可以获得的最大价值
    private int bestValue;

    public FZXJProblem(Knapsack[] bags, int totalWeight) {
        super();
        this.bags = bags;
        this.totalWeight = totalWeight;
        this.n = bags.length;
        // 物品依据单位重量价值进行排序
        Arrays.sort(bags, Collections.reverseOrder());
    }

    // 队列式分支限界法
    public void solve() {
        LinkedList<Node> nodeList = new LinkedList<Node>();

        // 起始节点当前重量和当期价值均为0
        nodeList.add(new Node(0, 0, 0));

        while (!nodeList.isEmpty()) {
            // 取出放入队列中的第一个节点
            Node node = nodeList.pop();

            if (node.upboundValue >= bestValue && node.index < n) {
                // 左节点:该节点代表物品放入背包中,上个节点的价值+本次物品的价值为当前价值
                int leftWeight = node.currWeight + bags[node.index].getWeight();
                int leftValue = node.currValue + bags[node.index].getValue();
                Node left = new Node(leftWeight, leftValue, node.index + 1);

                // 放入当前物品后可以获得的价值上限
                left.upboundValue = getUpboundValue(left);

                // 当物品放入背包中左节点的判断条件为保证不超过背包的总承重
                if (left.currWeight <= totalWeight
                        && left.upboundValue > bestValue) {
                    // 将左节点添加到队列中
                    nodeList.add(left);
                    if (left.currValue > bestValue) {
                        // 物品放入背包不超重,且当前价值更大,则当前价值为最大价值
                        bestValue = left.currValue;
                    }
                }

                // 右节点:该节点表示物品不放入背包中,上个节点的价值为当前价值
                Node right = new Node(node.currWeight, node.currValue,
                        node.index + 1);

                // 不放入当前物品后可以获得的价值上限
                right.upboundValue = getUpboundValue(right);

                if (right.upboundValue >= bestValue) {
                    // 将右节点添加到队列中
                    nodeList.add(right);
                }
            }
        }
    }

    // 当前操作的节点,放入物品或不放入物品
    class Node {
        // 当前放入物品的重量
        private int currWeight;
        // 当前放入物品的价值
        private int currValue;
        // 不放入当前物品可能得到的价值上限
        private int upboundValue;
        // 当前操作的索引
        private int index;

        public Node(int currWeight, int currValue, int index) {
            this.currWeight = currWeight;
            this.currValue = currValue;
            this.index = index;
        }
    }

    // 价值上限=节点现有价值+背包剩余容量*剩余物品的最大单位重量价值
    // 当物品由单位重量的价值从大到小排列时,计算出的价值上限大于所有物品的总重量,否则小于物品的总重量
    // 当放入背包的物品越来越来越多时,价值上限也越来越接近物品的真实总价值
    private int getUpboundValue(Node n) {

        // 获取背包剩余容量
        int surplusWeight = totalWeight - n.currWeight;
        int value = n.currValue;
        int i = n.index;

        while (i < this.n && bags[i].getWeight() <= surplusWeight) {
            surplusWeight -= bags[i].getWeight();
            value += bags[i].getValue();
            i++;
        }

        // 当物品超重无法放入背包中时,可以通过背包剩余容量*下个物品单位重量的价值计算出物品的价值上限
        if (i < this.n) {
            value += bags[i].getUnitValue() * surplusWeight;
        }

        return value;
    }

    public int getBestValue() {
        return bestValue;
    }

}

  最终测试结果:90 

public class FZXJTest {

    public static void main(String[] args) {
        Knapsack[] bags = new Knapsack[] { new Knapsack(2, 13),
                new Knapsack(1, 10), new Knapsack(3, 24), new Knapsack(2, 15),
                new Knapsack(4, 28), new Knapsack(5, 33), new Knapsack(3, 20),
                new Knapsack(1, 8) };
        int totalWeight = 12;
        FZXJProblem problem = new FZXJProblem(bags, totalWeight);

        problem.solve();
        System.out.println(problem.getBestValue());
    }

}
    原文作者:分支限界法
    原文地址: https://blog.csdn.net/ljmingcom304/article/details/50324007
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞