回溯法解決0-1揹包問題
阿新 • • 發佈:2019-01-10
問題描述:
有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