背包问题,相信各位看官肯定都有所耳闻!笔者就在此简单的描述一下背包问题:
给定一背包和n件物品,背包的容量为c,第i件物品的重量为w[i],价值为v[i](1<=i<=n);问装那些物品,可使得价值最大?
思路分析:显然,每种物品不外乎两种选择:装入和不装入背包!若将装入用状态1表示,不装入用状态0表示;那么就可构成一个二叉树!一共有n层,所以就可通过从第一层开始遍历搜索,在装入的物品总重量不大于c的情况下,找到最优解了!
思路还是蛮简单的,别的不多说,直接上代码:
1 public class Demo1 { 2 private static int c = 10; //背包容量 3 private static int[] w= {0,2,2,6,5,5}; //每个物体的重量:5个物体 4 private static int[] v = {0,6,3,5,4,6}; //每个物体的价值 5 private static int[] x= new int[6]; //存放每个物体的选中情况 6 private static int bestcp = 0; //当前的最优解 7 private static int[] bestcpArr = new int[6]; 8 private static int count = 0; 9 private static int n = 5; //物体的数目 10 public static void main(String[] args) { 11 backtrack(1, 0 ,0); 12 for(int i =0 ; i < bestcpArr.length ;i++){ 13 System.out.print(bestcpArr[i]); 14 } 15 System.out.println(""); 16 System.out.println(bestcp); 17 System.out.println("该树有" + count +"种遍历方式"); 18 } 19 /** 20 * 21 * @param i:查找到了第i个物品(i从1开始索引) 22 * @param cv:当前包中价值 23 * @param cw:当前包中重量 24 */ 25 private static void backtrack(int i , int cw , int cv) { 26 count++; 27 if (i > n) { 28 if (cv > bestcp) { 29 bestcp = cv; 30 for(int j = 0 ; j < 6 ;j++){ 31 bestcpArr[j]= x[j]; 32 } 33 } 34 }else{ 35 // 开始遍历,如果k=0,则表示不装下此物品,k=1,则表示装下此物品 36 for(int k = 0 ; k <= 1; k++){ 37 x[i] = k; 38 if (cw + w[i] <= c) { 39 cv += v[i] * k; 40 cw +=w[i] * k; 41 backtrack(i + 1, cw, cv); 42 cv -= v[i] * k; 43 cw -=w[i] * k; 44 } 45 } 46 } 47 } 48 }
我们知道,回溯法的效率并不是很高的!因为回溯法,就其本质而言,还是属于穷举!只不过它就提供了一个穷举的思路:回溯!的确也是这样,上面代码中的例子中的物品只由5个,换句话说,所构成的二叉树也只有5层!但当物品有10个,20个,甚至100个话,构成的二叉树是多么的庞大!(笔者觉得不可想象深度为100的二叉树)!故在上述代码的基础上,很有必要去做一个算法的优化!在网上看了别人的博客之后,大致知道了一个思路:即在不断的遍历左子树(即不断的将物品装入)的过程中,如果出现了不能再装入下一物品的情况时,这时就需要去遍历右子树!但是如果,如果在剩余容量情况下,将剩余容量的背包装满(如果大于0且小于物品的重量的话,按照单位重量的价值乘以剩余容量来算)的情况下 得到的总价值比当前最优解还要的小的话,那么该右子树是完全没有必要去遍历,而需要直接剪除的!这样就达到了优化的目的!代码如下:
1 /** 2 3 * 本算法的上界函数是这样定义的!首先利用冒泡法排序,按照单位价值右高到低开始顺序排列! 4 5 * 这样的话,就能保证,先装进去的是性价比(自己发明的,勿喷)最优的!而在上界函数中,也是如此,求的是剩余 6 7 * 容量在右子树所能容纳的最高价值(背包容量允许情况下),一旦小于当前最优解,那么就 8 9 * 没有继续遍历该右子树的必要 10 11 * 12 13 */ 14 15 public class Demo2 { 16 private static int n;// 物品数量 17 private static double c;// 背包容量 18 private static double[] v = new double[100];// 各个物品的价值 19 private static double[] w = new double[100];// 各个物品的重量 20 private static double cw = 0.0;// 当前背包重量 21 private static double cp = 0.0;// 当前背包中物品价值 22 private static double bestp = 0.0;// 当前最优价值 23 private static double[] perp = new double[100];// 单位物品价值排序后 24 private static int order[] = new int[100];// 物品编号 25 private static int[] put = new int[100];// 设置是否装入 26 27 // 按单位价值排序 28 private static void knapsack() { 29 int i, j; 30 int temporder = 0; 31 double temp = 0.0; 32 33 for (i = 1; i <= n; i++) 34 perp[i] = v[i] / w[i]; 35 for (i = 1; i <= n - 1; i++) { 36 for (j = i + 1; j <= n; j++) 37 if (perp[i] < perp[j])// 冒泡排序perp[],order[],sortv[],sortw[] 38 { 39 temp = perp[i]; 40 perp[i] = perp[i]; 41 perp[j] = temp; 42 43 temporder = order[i]; 44 order[i] = order[j]; 45 order[j] = temporder; 46 temp = v[i]; 47 v[i] = v[j]; 48 v[j] = temp; 49 50 temp = w[i]; 51 w[i] = w[j]; 52 w[j] = temp; 53 } 54 } 55 } 56 57 // 回溯函数 58 private static void backtrack(int i) { 59 if (i > n) { 60 bestp = cp; 61 return; 62 } 63 // 将物品装进背包:此种情况的话 64 if (cw + w[i] <= c) { 65 cw += w[i]; 66 cp += v[i]; 67 put[i] = 1; 68 backtrack(i + 1); 69 cw -= w[i]; 70 cp -= v[i]; 71 } 72 if (bound(i + 1) > bestp)// 符合条件搜索右子数 73 backtrack(i + 1); 74 } 75 76 // 计算上界函数 77 78 //算法剩余容量的情况下最多能装的价值 79 private static double bound(int i) { 80 double leftw = c - cw; 81 double b = cp; 82 while (i <= n && w[i] <= leftw) { 83 leftw -= w[i]; 84 b += v[i]; 85 i++; 86 } 87 if (i <= n) 88 b += v[i] / w[i] * leftw; 89 return b; 90 91 } 92 93 public static void main(String[] args) { 94 // private static int[] w= {0,2,2,6,5,5}; //每个物体的重量:5个物体 95 // private static int[] v = {0,6,3,5,4,6}; //每个物体的价值 96 v[0] = 0; 97 v[1] = 6; 98 v[2] = 3; 99 v[3] = 5; 100 v[4] = 4; 101 v[5] = 6; 102 103 w[0] = 0; 104 w[1] = 2; 105 w[2] = 2; 106 w[3] = 6; 107 w[4] = 5; 108 w[5] = 5; 109 110 n = 5; 111 c = 10; 112 113 knapsack(); 114 115 backtrack(1); 116 System.out.println(bestp); 117 for (int i = 1; i <= n; i++) { 118 System.out.print(put[i] + " "); 119 } 120 System.out.println(""); 121 } 122 }
现在,我们来分析下时间复杂度,最好的情况当然是能够剪除所有的右子树,而最优的物品选择刚好全在左子树上了!而最坏的情况自然是搜索右子树的次数最多了呀!
到此,直接遍历穷举和优化之后的回溯法来解决10背包问题的分析解答终于写完了!写了一上午啊!
笔者水平有限,有纰漏之处勿喷!
浙公网安备 33010602011771号