完全背包问题
一个旅行者随身携带一个背包. 可以放入背包的物品有n 种, 每种物品的重量和价值分别为 wi , vi . 如果背包的最大重量限制是 b, 每种物品可以放多个. 怎样选择放入背包的物品以使得背包的价值最大 ? 不妨设上述 wi , vi , b 都是正整数.
解题思路
目标函数:装入背包的所有物品的价值达到最大
约束条件:装入背包的所有物品的重量<=b
F[k][y] 表示只允许装入前k种物品,且背包重量不超过y时背包内所有物品的最大价值。此时有两种选择:
- 不装第k种物品,此时只能从前k-1件物品中选,F[k][y] = F[k-1][y]
第k种物品至少装一件,(如果先装入一件第k种物品,此时剩余重量y-wk,由于每种物品可以装入多件,所以任然要在前k种物品中选择)此时F[k][y] = F[k][y-wk] + vk;
要让背包内物品价值达到最大,则递推方程:F[k][y] = max{F[k-1][y] , F[k][y-wk] + vk}
边界条件:
F[0][y] = 0;
F[k][0] = 0;
F[k][y] = 负无穷, if y<0
F[1][y] = (y/w1)*v1;
标记函数mark[k][y] 用于标记当F[k][y] 价值达到最大时,背包里装入物品的最大编号(此函数需要从后向前遍历==)
如果:F[k-1][y] > F[k][y-wk] + vk,则mark[k][y] =mark[k-1][y]
如果:F[k-1][y] < F[k][y-wk] + vk,则mark[k][y] =k
边界条件:
mark[k][0] = 0
mak[0][k] = 0
时空复杂度
备忘录F[k][y] 中,k与y的组合有 n*b种,每种的计算时间为常数
时间复杂度:O(n*b)
空间复杂度:O(n*b)
实现
package com.cn;
import java.util.Scanner;
public class KnapsackProblem {
public static void main(String []args){
int n = 0;//the number of the kinds of goods
int b = 0;//backpack maximum weight limit
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
b = sc.nextInt();
int[] w = new int[n+1];//the weight of each goods
int[] v = new int[n+1];//the value of each goods
int[][]mark = new int[n+1][b+1];
for(int i = 1;i <= n;i ++){
if(sc.hasNextInt()){
w[i] = sc.nextInt();//实际存储的下标是从1开始的
}
}
for(int i = 1;i<=n;i++){
if(sc.hasNextInt()){
v[i] = sc.nextInt();//实际存储的下标是从1开始的
}
}
System.out.println(MaxValue(n,b,w,v,mark));
Sign(mark,n,b,w);
}
public static int MaxValue(int n,int b,int[] w,int[] v,int[][] mark){
int[][] F = new int[n+1][b+1];//实际存储的下标是从1开始的
for(int i=1;i<=b;i++){
F[0][i] = 0;
F[1][i] = i/w[1]*v[1];
mark[0][i] = 0;
}
for(int i=1;i<=n;i++){
F[i][0] = 0;
mark[i][0] = 0;
}
for(int k=1;k<=n;k++){
for(int y=1;y<=b;y++){
if(y < w[k]){
F[k][y] = F[k-1][y];
mark[k][y] = mark[k-1][y];
}else{
F[k][y] = F[k-1][y] > (F[k][y-w[k]]+v[k]) ? F[k-1][y] : (F[k][y-w[k]]+v[k]);
mark[k][y] = F[k-1][y] > (F[k][y-w[k]]+v[k]) ? mark[k-1][y] : k;
}
}
}
return F[n][b];
}
//计算最终装进的每件物品的数量
public static void Sign(int[][] mark,int n,int b,int[] w){
int[] num = new int[n+1];
for(int i = 1;i<=n;i++){
num[i] = 0;
}
int s = mark[n][b];
int t = b;
while(mark[s][t] != 0){
t = t - w[s];
num[s]++;
while(mark[s][t] == s){
num[s]++;
t = t - w[s];
}
s = mark[s][t];
}
for(int i=1;i<=n;i++){
System.out.println("item" + i + ":" + num[i]);
}
}
}
空间优化
F[k][y] = max{F[k-1][y] , F[k][y-wk] + vk}
使用F[y]来表示F[k][y]、以及F[k-1][y]
使用F[y-wk]来表示F[k][y-wk]
package com.cn;
import java.util.Scanner;
public class KnapsackProblemOptimization {
public static void main(String[] args) {
// TODO Auto-generated method stub
int n = 0;//the number of the kinds of goods
int b = 0;//backpack maximum weight limit
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
b = sc.nextInt();
int[] w = new int[n+1];//the weight of each goods
int[] v = new int[n+1];//the value of each goods
int[][]mark = new int[n+1][b+1];
for(int i = 1;i <= n;i ++){
if(sc.hasNextInt()){
w[i] = sc.nextInt();
}
}
for(int i = 1;i<=n;i++){
if(sc.hasNextInt()){
v[i] = sc.nextInt();
}
}
System.out.println(MaxValue(n,b,w,v,mark));
}
public static int MaxValue(int n,int b,int[] w,int[] v,int[][] mark){
int[] F = new int[b+1];
for(int i=1;i<=b;i++){
F[i] = i/w[1]*v[1];
mark[0][i] = 0;
}
for(int i=1;i<=n;i++){
F[i] = 0;
mark[i][0] = 0;
}
for(int k=2;k<=n;k++){
for(int y=1;y<=b;y++){
if(y < w[k]){
mark[k][y] = mark[k-1][y];
}else if(F[y-w[k]]+v[k] >= F[y]){
F[y] = F[y-w[k]]+v[k];
mark[k][y] = k;
}
}
}
return F[b];
}
}
0-1背包问题
0-1背包问题与完全背包问题的区别就是:每种物品只有一件(要么放进背包,要么不放)
解题思路
完全按照上面的思路,但是:
F[k][y]任然有两种选择:
- 不装第k种物品:F[k][y] = F[k-1][y]
- 装第k种物品:F[k][y] = F[k-1][y-wk] + vk
此时F[k][y] = max{F[k-1][y] , F[k-1][y-wk] + vk}
边界条件:
F[0][y] = 0;
F[k][0] = 0;
F[k][y] = 负无穷, if y<0
F[1][y] = v1 , if y>w1 ,=0, if y
实现
package com.cn;
import java.util.Scanner;
public class KnapSack01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int n = 0;//the number of the kinds of goods
int b = 0;//backpack maximum weight limit
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
b = sc.nextInt();
int[] w = new int[n+1];//the weight of each goods
int[] v = new int[n+1];//the value of each goods
int[][]mark = new int[n+1][b+1];
for(int i = 1;i <= n;i ++){
if(sc.hasNextInt()){
w[i] = sc.nextInt();
}
}
for(int i = 1;i<=n;i++){
if(sc.hasNextInt()){
v[i] = sc.nextInt();
}
}
System.out.println(MaxValue(n,b,w,v,mark));
Sign(mark,n,b,w);
}
public static int MaxValue(int n,int b,int[] w,int[] v,int[][] mark){
int[][] F = new int[n+1][b+1];
for(int i=1;i<=b;i++){
F[0][i] = 0;
if(i >= w[1]){
F[1][i] = v[1];
}else{
F[1][i] = 0;
}
mark[0][i] = 0;
}
for(int i=1;i<=n;i++){
F[i][0] = 0;
mark[i][0] = 0;
}
for(int k=1;k<=n;k++){
for(int y=1;y<=b;y++){
if(y < w[k]){
F[k][y] = F[k-1][y];
mark[k][y] = mark[k-1][y];
}else{
F[k][y] = F[k-1][y] > (F[k-1][y-w[k]]+v[k]) ? F[k-1][y] : (F[k-1][y-w[k]]+v[k]);
mark[k][y] = F[k-1][y] > (F[k][y-w[k]]+v[k]) ? mark[k-1][y] : k;
}
}
}
return F[n][b];
}
//计算最终装进的每件物品的数量
public static void Sign(int[][] mark,int n,int b,int[] w){
int s = mark[n][b];
int t = b;
while(mark[s][t] != 0){
System.out.println("item" + s);
t = t - w[s];
s = mark[s][t];
}
}
}
二维0-1背包问题
二维背包问题:每件物品有重量 wi 和体积 ti, i =1, 2, … , n,背包总重不超过 b,体积不超过V, 如何选择物品以得到最大价值.
递推方程
若j>=wi 且 k>=ci
m[i,j,k] = max{m[i-1,j,k] , m[i-1,j-wi,k-ci] + vi}
否则
m[i,j,k] = m[i-1,j,k]
以上内容整理自屈婉玲老师的算法设计与分析,如有不足,欢迎交流