1、0/1背包问题:在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。
递推公式:F(i, j) = Max{ F(i-1, j)+ F(i-1, j-wi)+pi }
原理分析:考虑第i件物品,假设不加入,最大值则为放入i-1件时的最大值;假设加入,最大值则为当前值+剩余空间的最大值,最终两者情况相比取其大。子问题也遵循相同的原理。
2、代码实现:
(1)递归实现:
import java.util.Scanner; /** * * @author * 0/1背包问题 */ public class Main9 { public static void main(String args[]) { Scanner s = new Scanner(System.in); /** * 定义测试数据 */ int w[] = {1,5,2,3,4}; int v[] = {5,7,2,6,100}; int len = w.length; int topW = 8; int topV = findMaxVal(w,v,len-1,topW); System.out.println(topV); s.close(); } public static int findMaxVal(int[] w,int[] v,int cur_index,int cur_topw) { /* * 递归结束条件:没有物品可取 */ if(cur_index<0) { return 0; } //假设不取第i件物品 int lastMax = findMaxVal(w, v,cur_index-1, cur_topw); int curMax = lastMax; if(w[cur_index]<=cur_topw) { //假设取第i件物品 curMax = Math.max(lastMax, findMaxVal(w,v,cur_index-1,(cur_topw-w[cur_index]))+v[cur_index]); } return curMax; } }
(2)递归遍历+记忆搜索:当第一次计算时记录值。缩小遍历次数。
import java.util.Scanner; public class Main9_2 { public static void main(String args[]) { Scanner s = new Scanner(System.in); /** * 定义测试数据 */ int w[] = {1,5,2,3,4}; int v[] = {5,7,2,6,1}; int len = w.length; int topW = 8; int keep[][] = new int[len][topW+1]; //先将记录数组初始化为-1. for(int i=0;i<len;i++) { for(int j=0;j<=topW;j++) { keep[i][j] = -1; } } int topV = findMaxVal(w,v,len-1,topW,keep); System.out.println(topV); s.close(); } public static int findMaxVal(int[] w,int[] v,int cur_index,int cur_topw,int keep[][]) { /* * 递归结束条件:选择到最后一件物品 */ if(cur_index==0) { int res = w[0]<=cur_topw?v[0]:0; return res; } //假设不取第i件物品,查询子问题是否被记录 int lastMax= keep[cur_index-1][cur_topw]==-1? findMaxVal(w, v,cur_index-1, cur_topw,keep):keep[cur_index-1][cur_topw]; int curMax = lastMax; if(w[cur_index]<=cur_topw) { //假设取第i件物品,递归之前查询是否被记录。 int temp = keep[cur_index-1][cur_topw-w[cur_index]]==-1?findMaxVal(w,v,cur_index-1,(cur_topw-w[cur_index]),keep): keep[cur_index-1][cur_topw-w[cur_index]]; curMax = Math.max(lastMax,temp+v[cur_index]); } return curMax; } }
(3)多重循环实现:使用二维数组记录结果
import java.util.Scanner; public class Main9_3 { public static void main(String args[]) { Scanner s = new Scanner(System.in); //定义测试数据 int w[] = {1,5,2,3,4}; int v[] = {5,7,2,6,1}; int len = w.length; int topW = 8; int keep[][] = new int[len][topW+1]; for(int i=0;i<=topW;i++) { keep[0][i] = w[0]<=i?v[0]:0; } //开始填表(二维数组) for(int i=1;i<len;i++) { for(int j=0;j<=topW;j++) { keep[i][j] = keep[i-1][j]; if(w[i]<=j) { keep[i][j] = Math.max(keep[i][j], keep[i-1][j-w[i]]+v[i]); } } } //打印填表结果,实际可省略 for(int i=0;i<len;i++) { for(int j=0;j<=topW;j++) { System.out.print(keep[i][j]+" "); } System.out.println(); } System.out.println(keep[len-1][topW]); s.close(); } }
(4)多重循环实现:使用一维循环数组记录当前结果。
》要点:记录当前值时,需要从后往前更新(这也是循环的体现)。
package yrc1; import java.util.Scanner; public class Main9_4 { public static void main(String args[]) { //最后结果:0 5 5 7 11 11 13 13 14 Scanner s = new Scanner(System.in); //定义测试数据 int w[] = {1,5,2,3,4}; int v[] = {5,7,2,6,1}; int len = w.length; int topW = 8; int keep[] = new int[topW+1]; for(int i=0;i<=topW;i++) { keep[i] = w[0]<=i?v[0]:0; } for(int i=1;i<len;i++) { for(int j=topW;j>=w[i];j--) { keep[j] = Math.max(keep[j], keep[j-w[i]]+v[i]); } } //显示最后记录结果 for(int i=0;i<=topW;i++) { System.out.print(keep[i]+" "); } System.out.println(" "+keep[topW]); } }