回溯法解决0-1背包问题

时间:2022-08-03 18:42:31

问题描述:

  有n件物品和一个容量为c的背包。第i件物品的价值是v[i],重量是w[i]。求解将哪些物品装入背包可使价值总和最大。所谓01背包,表示每一个物品只有一个,要么装入,要么不装入。
回溯法:

  01背包属于找最优解问题,用回溯法需要构造解的子集树。在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其减去,剪枝啦啦!
  
上界函数bound():当前价值cw+剩余容量可容纳的最大价值<=当前最优价值bestp。
为了更好地计算和运用上界函数剪枝,选择先将物品按照其单位重量价值从大到小排序,此后就按照顺序考虑各个物品。

不考虑剪枝:

import java.util.Scanner;

public class Main2 {
    static int n;
    static int c;
    static int[] weight;
    static int[] price;
    static int bA[];// 当前最优解
    static int bp = 0;// 当前最大价值

    static int currentWeight = 0;// 当前重量
    static int currentPrice = 0;// 当前价值

    static int[] bestAnswer;// 解
    static int bestPrice = 0;// 最大价值


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("输入背包数量n:");
        n = sc.nextInt();
        System.out.println("输入背包容量c:");
        c = sc.nextInt();
        System.out.println("输入" + n + "个物品的重量:");
        weight = new int[n];
        for (int i = 0; i < n; i++) {
            weight[i] = sc.nextInt();
        }
        System.out.println("输入" + n + "个物品的价值:");
        price = new int[n];
        for (int i = 0; i < n; i++) {
            price[i] = sc.nextInt();
        }

        bA = new int[n];
        bestAnswer = new int[n];

        System.out.println("各符合条件的路径为:");
        backTracnking(0);
        System.out.println("---------------------");
        System.out.println("最优方案为:");
        for (int i = 0; i < n; i++) {
            System.out.print(bA[i] + " ");
        }
        System.out.println("最优价值为:");
        System.out.print(bp);
    }

    private static void backTracnking(int i) {
        if (i >= n) {// 递归退出条件
            Print();// 打印当前方案
            if (bestPrice > bp) {// 判断当前方案是否由于最优解
                bp = bestPrice;
                for (int j = 0; j < n; j++) {
                    bA[j] = bestAnswer[j];
                }
            }
            return;
        }

        if (currentWeight + weight[i] <= c) {//装
            bestAnswer[i] = 1;
            currentWeight += weight[i];
            bestPrice += price[i];
            backTracnking(i + 1); // 递归下一层
            currentWeight -= weight[i]; // 回溯
            bestPrice -= price[i];
        } 
        //不装
        bestAnswer[i] = 0;
        backTracnking(i + 1);
    }

    private static void Print() {
        System.out.print("路径为:");
        for (int i = 0; i < n; i++) {
            System.out.print(bestAnswer[i]);
        }
        System.out.println(" 价值为" + bestPrice);
    }

}
输入背包数量n:
4
输入背包容量c:
10
输入4个物品的重量:
7 3 4 5
输入4个物品的价值:
42 12 40 25
各符合条件的路径为:
路径为:1100 价值为54
路径为:1000 价值为42
路径为:0110 价值为52
路径为:0101 价值为37
路径为:0100 价值为12
路径为:0011 价值为65
路径为:0010 价值为40
路径为:0001 价值为25
路径为:0000 价值为0 ---------------------
最优方案为:
0 0 1 1 最优价值为:
65

考虑 剪枝:
为了排序方便将每个货物封装成一个类

自定义类的数组,一定要初始化,不然数组的每个元素都为null
自定义类的数组克隆,一定要一个元素一个元素克隆,自定义类要实现implements Cloneable ,并重写克隆方法

public class Main2 {
    static int n;
    static int c;
    static Good[] goods;// 原始数据
    static int currentBestPrice = 0;// 当前最大价值

    static int currentWeight = 0;// 当前背包重量
    static int currentPrice = 0;// 当前背包价值

    static Good[] bestAnswer;// 全局最优解
    static int bestPrice = 0;// 全局最大价值

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("输入背包数量n:");
        n = sc.nextInt();
        System.out.println("输入背包容量c:");
        c = sc.nextInt();

        // 初始化
        goods = new Good[n];
        for (int i = 0; i < n; i++) {
            goods[i] = new Good();
        }
        bestAnswer = new Good[n];
        for (int i = 0; i < n; i++) {
            bestAnswer[i] = new Good();
        }

        System.out.println("输入" + n + "个物品的重量:");
        for (int i = 0; i < n; i++) {
            goods[i].weight = sc.nextInt();
        }
        System.out.println("输入" + n + "个物品的价值:");
        for (int i = 0; i < n; i++) {
            goods[i].price = sc.nextInt();
            goods[i].no = i;
            goods[i].unitprice = goods[i].price / goods[i].weight;
        }

        Arrays.sort(goods, new Comparator<Good>() {// 由单位重量价值升序排序
            @Override
            public int compare(Good o1, Good o2) {
                return (int) (o2.unitprice - o1.unitprice);
            }

        });

        System.out.println("各符合条件的路径为:");
        backTracnking(0);

        System.out.println("---------------------");
        System.out.println("最优方案为:");
        Print(bestAnswer, bestPrice);
    }

    private static void backTracnking(int i) {
        if (i >= n) {// 递归退出条件
            Print(goods, currentBestPrice);// 打印当前方案
            if (currentBestPrice > bestPrice) {// 判断当前方案是否由于最优解
                bestPrice = currentBestPrice;
                for (int j = 0; j < n; j++) {
                    bestAnswer[j] = (Good) goods[j].clone();
                }
                // Arrays.copyOf(goods, n, bestAnswer);
            }
            return;
        }

        if (currentBestPrice + bound(i) < bestPrice) {// 上界函数,剪枝,如果上界函数小于当前已知最大价值,则不用递归了
            return;
        }

        if (currentWeight + goods[i].weight <= c) {// 装
            goods[i].put = 1;
            currentWeight += goods[i].weight;
            currentBestPrice += goods[i].price;
            backTracnking(i + 1); // 递归下一层
            goods[i].put = 0;// 回溯
            currentWeight -= goods[i].weight;
            currentBestPrice -= goods[i].price;
        }
        // 不装
        goods[i].put = 0;
        backTracnking(i + 1);
    }

    private static float bound(int x) {
        float restPrice = 0;
        int thisWeight = currentWeight;
        while (thisWeight > 0) {
            if (goods[x].weight > thisWeight) {
                break;
            }
            restPrice = restPrice + goods[x].price;
            thisWeight = thisWeight - goods[x].weight;
            x++;
            if (x >= n) {
                break;
            }
        }
        if (thisWeight > 0 && x < n) {
            restPrice = restPrice + thisWeight * goods[x].unitprice;
        }
        return restPrice;
    }

    private static void Print(Good[] thisgoods, int price) {
        Good[] printgoods = thisgoods.clone();
        Arrays.sort(printgoods, new Comparator<Good>() {
            @Override
            public int compare(Good o1, Good o2) {
                return o1.no - o2.no;
            }

        });
        System.out.print("路径为:");
        for (int i = 0; i < n; i++) {
            System.out.print(printgoods[i].put);
        }
        System.out.println(" 价值为" + price);
    }

}

class Good implements Cloneable {
    int no;
    int weight;
    int price;
    float unitprice;// 单位重量价值
    int put;// 是否放入背包

    @Override
    public Object clone() {
        Good g = new Good();
        try {
            g = (Good) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return g;
    }
}
输入背包数量n:
3
输入背包容量c:
20
输入3个物品的重量:
11 8 6
输入3个物品的价值:
18 25 20
各符合条件的路径为:
路径为:011 价值为45 ---------------------
最优方案为:
路径为:011 价值为45