问题描述:
有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