1. 程式人生 > >回溯法解決0-1揹包問題

回溯法解決0-1揹包問題

問題描述:

  有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