1.问题描述
设n件物品的重量分别为w1,w2,...,wn,其对应的价值分别为v1,v2,...,vn。选取若干物品装入背包中,限制总重量为W,求能获取的最大价值。
2.回溯法求解
解题思路
(1)开始递归,判断当前下标是否等于最大物品数以及当前重量是否超过最大重量限制
(2)若等于最大物品数或大于最大重量限制
若重量小于最大重量限制
判断物品价值是否大于已知的最大价值
若大于,更新最大价值及最大价值对应的物品,返回上一层递归
若不大于什么都不做,返回上一层递归
若重量大于最大重量价值,什么也不做,返回上一层递归
(3)若小于最大物品数且小于最大重量限制
下标加一修改访问标记,重量和价值作为参数进入下一层递归,即选择当前下标的物品
dfs(index+1,weight+weights[index+1],value+values[index+1],visited);
从下一层递归返回,修改的访问标记回滚
只修改下标(加一),传入参数,进入同层的下一个递归,即不选择当前下标的物品
dfs(index+1,weight,value,visited);
(4)递归结束,输出结果
实现代码
/**
* 0/1背包问题回溯解法
* @author lzy
*
*/
public class PackBack {
/* 物品个数 */
private int num;
/* 物品重量 */
private int[] weights;
/* 物品价值 */
private int[] values;
/* 最终解 */
private boolean[] result;
/* 最大重量 */
private int maxValue;
/* 重量限制 */
private int limit;
public PackBack(int num,int limit,int[] weights,int[] values) {
this.num = num;
this.limit = limit;
this.weights = weights;
this.values = values;
this.result = new boolean[this.num+1];
this.maxValue = 0;
}
public void dfs(int index,int weight,int value,boolean[] choices) {
/* 判断是否结束递归 */
if(index>this.num||weight>this.limit) {
if(weight<=this.limit&&value>this.maxValue) {
this.maxValue = value;
/* 记录结果 */
for(int i = 1;i<=this.num;i++) {
this.result[i] = choices[i];
}
}
}else {
choices[index] = true;
/* 进入下一层递归 */
dfs(index+1,weight+this.weights[index],value+this.values[index],choices);
choices[index] = false;
/* 本层按下标递归,不会重复 */
dfs(index+1,weight,value,choices);
}
}
/* 调用dfs获取答案 */
public void solute() {
boolean[] choices = new boolean[this.num+1];
for(int i = 0;i<=this.num;i++) {
choices[i] = false;
}
dfs(1,0,0,choices);
}
/* 获取最大价值 */
public int getMaxValue() {
return this.maxValue;
}
/* 获取最终背包中物品数组 */
public int[] getResults() {
int count = 0;
for(int i = 0;i<=this.num;i++) {
if(this.result[i]) {
count++;
}
}
int[] answer = new int[count];
count = 0;
for(int i = 0;i<=this.num;i++) {
if(this.result[i]) {
answer[count] = this.weights[i];
count++;
}
}
return answer;
}
}
3.分支定界法求解
解题思路
(1)根据价值/重量的值从大到小对重量和价值数组进行排序
(2)构造初始根节点,节点属性包括节点在搜索空间中的层次(即遍历到的物品的下标),节点重量,价值,当前背包中的总重量,总价值,背包中的物品等,根节点层次,重量,价值均为0
(3)计算结点上界限,方法如下:根据当前结点所在层次,向后遍历物品重量数组,若能装下,直接加上其价值,若不能装下,将该物品拆开装满背包并停止遍历。此时的总价值即为上界。
(4)开始循环,优先级队列判空
若队列为空停止循环
若队列非空,取出优先级最高的结点,判断是否为叶子结点
若为叶子结点,若总价值大于已知的最大价值,则更新最大价值及对应的物品
若非叶子结点,生成该节点的左子节点(即装入下一个下标的物品)
根据下一个坐标对应的物品的属性修改节点中对应的属性,并计算节点的上级界
若上界小于最大价值,剪枝,若大于最大价值则将该结点加入优先级队列
继续生成该结点的右子节点,即不选择装入下一个坐标的物品,并正确设置该结点 的各项属性值。计算该结点的上界,决定剪枝还是将其加入优先级队列。
进入下一次循环。
(5)循环结束,输出结果
实现代码
/**
* 0/1背包问题分支定界法解法
* @author lzy
*
*/
public class PackBB4 {
private class Node {
/* 当前节点在搜索空间中的层次 */
int index;
/* 当前节点重量 */
int weight;
/* 当前节点价值 */
int value;
/* 当前背包中物品总质量 */
int totalWeight;
/* 当前背包中物品总价值 */
int totalValue;
/* 背包中的物品 */
boolean[] goods;
/* 上界 */
double ub;
public Node() {
goods = new boolean[num + 1];
}
}
/* 物品总数 */
private int num;
/* 物品重量数组 */
private int[] weights;
/* 物品价值数组 */
private int[] values;
/* 物品重量价值比 */
private double[] vw;
/* 最终背包中的物品 */
private boolean[] result;
/* 物品最大重量限制 */
private int limit;
/* 最大价值 */
private int maxValue;
/* 层级高(靠近终点)的先出队 */
private Comparator<Node> cmp = new Comparator<Node>() {
public int compare(Node node1, Node node2) {
return node2.index - node1.index;
}
};
/* 优先级队列 */
private PriorityQueue<Node> queue = new PriorityQueue<Node>(100, cmp);
public PackBB4(int num, int limit, int[] weights, int[] values) {
this.num = num;
this.limit = limit;
this.weights = weights;
this.values = values;
this.maxValue = 0;
this.vw = new double[this.num + 1];
result = new boolean[this.num + 1];
for (int i = 1; i <= this.num; i++) {
result[i] = false;
this.vw[i] = (double) this.values[i] / this.weights[i];
}
sort(this.weights, this.values, this.vw);
}
/**
* 根据价值重量比降序排序
*
* @param weights
* @param values
* @param vw
*/
public void sort(int[] weights, int[] values, double[] vw) {
int temp1;
double temp2;
for (int i = this.num; i >= 1; i--) {
for (int j = 1; j < i; j++) {
if (vw[j] < vw[j + 1]) {
temp2 = vw[j];
vw[j] = vw[j + 1];
vw[j + 1] = temp2;
temp1 = weights[j];
weights[j] = weights[j + 1];
weights[j + 1] = temp1;
temp1 = values[j];
values[j] = values[j + 1];
values[j + 1] = temp1;
}
}
}
}
/**
* 计算一个节点的上界
*
* @param node
*/
public void bound(Node node) {
int index = node.index + 1;
int totalWeight = node.totalWeight;
int totalValue = node.totalValue;
while (index <= this.num && totalWeight + this.weights[index] <= this.limit) {
totalWeight += this.weights[index];
totalValue += this.values[index];
index++;
}
if (index <= this.num) {
node.ub = totalValue + (this.limit - totalWeight) * vw[index];
} else {
node.ub = totalValue;
}
}
public void solute() {
Node node;
Node node1;
Node node2;
node = new Node();
node.index = 0;
node.weight = 0;
node.value = 0;
node.totalWeight = 0;
node.totalValue = 0;
for (int i = 1; i <= this.num; i++) {
node.goods[i] = false;
}
this.bound(node);
this.queue.add(node);
//int ct = 0;
while (!this.queue.isEmpty()) {
//ct++;
node = this.queue.poll();
/* 是叶子节点 */
if (node.index == this.num) {
if (node.totalValue > this.maxValue) {
this.maxValue = node.totalValue;
for (int i = 1; i <= this.num; i++) {
this.result[i] = node.goods[i];
}
}
} else {
/* 左节点(选择下标为node.index+1的物品) */
if (node.totalWeight + this.weights[node.index + 1] <= this.limit) {
node1 = new Node();
node1.index = node.index + 1;
for (int i = 1; i <= this.num; i++) {
node1.goods[i] = node.goods[i];
}
node1.goods[node1.index] = true;
node1.weight = this.weights[node1.index];
node1.value = this.values[node1.index];
node1.totalWeight = node.totalWeight + node1.weight;
node1.totalValue = node.totalValue + node1.value;
this.bound(node1);
if (node1.ub > this.maxValue) {
queue.add(node1);
}
}
/* 右节点(选择下标为node.index+1的物品) */
node2 = new Node();
node2.index = node.index + 1;
for (int i = 1; i <= this.num; i++) {
node2.goods[i] = node.goods[i];
}
node2.weight = node.weight;
node2.value = node.value;
node2.totalWeight = node.totalWeight;
node2.totalValue = node.totalValue;
this.bound(node2);
if (node2.ub > this.maxValue) {
queue.add(node2);
}
}
}
// System.out.println(ct);
}
/* 获取最大价值 */
public int getMaxValue() {
return this.maxValue;
}
/* 获取最终背包中物品数组 */
public int[] getResults() {
int count = 0;
for (int i = 0; i <= this.num; i++) {
if (this.result[i]) {
count++;
}
}
int[] answer = new int[count];
count = 0;
for (int i = 0; i <= this.num; i++) {
if (this.result[i]) {
answer[count] = this.weights[i];
count++;
}
}
return answer;
}
}
4.动态规划法求解
解题思路
(1)初始化dp[][]数组, dp[i][j]代表在前i个物品中选取不大于j的最大价值,dp[0][j]和dp[j][0]均赋值为0
(2)顺序遍历dp[][]数组,递推公式dp[i][j] = max(dp[i-1][j],dp[i-1][j-weights[i]]+ values [i])
(3)返回dp[this.num][this.limit]作为结果,num为物品数量,limit为最大限制重量
实现代码
/**
* 0/1背包问题动态规划解法
* @author lzy
*
*/
public class PackDP {
/* 物品数量 */
private int num;
/* 物品重量限制 */
private int limit;
/* 物品重量数组 */
private int[] weights;
/* 物品价值数组 */
private int[] values;
/* dp[i][j]代表在前i个物品中选取重量不大于j的最大价值 */
private int[][] dp;
public PackDP(int num, int limit, int[] weights, int[] values) {
this.num = num;
this.limit = limit;
this.weights = weights;
this.values = values;
this.dp = new int[this.num + 1][this.limit + 1];
for (int i = 0; i <= this.num; i++) {
dp[i][0] = 0;
}
for (int j = 0; j <= this.limit; j++) {
dp[0][j] = 0;
}
}
public int solute() {
for (int i = 1; i <= this.num; i++) {
for (int j = 1; j <= this.limit; j++) {
/* 防止越界 */
if(j-this.weights[i]>=0) {
/*
* dp[i][j]代表在前i个物品中选取不大于j的最大价值
* 递推公式dp[i][j] = max(dp[i-1][j],dp[i-1][j-this.weights[i]]+this.values[i])
*/
dp[i][j] = this.max(dp[i-1][j], dp[i-1][j-this.weights[i]]+this.values[i]);
}else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[this.num][this.limit];
}
public int max(int number1, int number2) {
return number1 > number2 ? number1 : number2;
}
}
写得很烂请见谅哈!