算法学习笔记(二)——01背包问题之回溯解法

Posted on 2016-04-04 10:41  Iltxy  阅读(572)  评论(0)    收藏  举报

  背包问题,相信各位看官肯定都有所耳闻!笔者就在此简单的描述一下背包问题:

给定一背包和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 }
View Code

  我们知道,回溯法的效率并不是很高的!因为回溯法,就其本质而言,还是属于穷举!只不过它就提供了一个穷举的思路:回溯!的确也是这样,上面代码中的例子中的物品只由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 }
View Code
   现在,我们来分析下时间复杂度,最好的情况当然是能够剪除所有的右子树,而最优的物品选择刚好全在左子树上了!而最坏的情况自然是搜索右子树的次数最多了呀!
 到此,直接遍历穷举和优化之后的回溯法来解决10背包问题的分析解答终于写完了!写了一上午啊!
     笔者水平有限,有纰漏之处勿喷!
 

博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3