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

来源:互联网 发布:keilc51下载西西软件 编辑:程序博客网 时间:2024/06/03 15:59
背包问题,相信各位看官肯定都有所耳闻!笔者就在此简单的描述一下背包问题:
给定一背包和n件物品,背包的容量为c,第i件物品的重量为w[i],价值为v[i](1<=i<=n);问装那些物品,可使得价值最大?
        思路分析:显然,每种物品不外乎两种选择:装入和不装入背包!若将装入用状态1表示,不装入用状态0表示;那么就可构成一个二叉树!一共有n层,所以就可通过从第一层开始遍历搜索,在装入的物品总重量不大于c的情况下,找到最优解了!
思路还是蛮简单的,别的不多说,直接上代码:

public class Demo1 {private static int c = 10; //背包容量private static int[] w= {0,2,2,6,5,5};//每个物体的重量:5个物体private static int[] v = {0,6,3,5,4,6};//每个物体的价值private static int[] x= new int[6];//存放每个物体的选中情况private static int bestcp = 0;//当前的最优解private static int[] bestcpArr = new int[6];private static int count = 0;private static int n = 5;//物体的数目public static void main(String[] args) {backtrack(1, 0 ,0);for(int i =0 ; i < bestcpArr.length ;i++){System.out.print(bestcpArr[i]);}System.out.println("");System.out.println(bestcp);System.out.println("该树有" + count +"种遍历方式");}/** * * @param i:查找到了第i个物品(i从1开始索引) * @param cv:当前包中价值 * @param cw:当前包中重量 */private static void backtrack(int i , int cw , int cv) {count++;if (i > n) {if (cv > bestcp) {bestcp = cv;for(int j = 0 ; j < 6 ;j++){bestcpArr[j]= x[j];}}}else{//开始遍历,如果k=0,则表示不装下此物品,k=1,则表示装下此物品for(int k = 0 ; k <= 1; k++){x[i] = k;if (cw + w[i] <= c) {cv += v[i] * k;cw +=w[i] * k;backtrack(i + 1, cw, cv);cv -= v[i] * k;cw -=w[i] * k;}}}}}

我们知道,回溯法的效率并不是很高的!因为回溯法,就其本质而言,还是属于穷举!只不过它就提供了一个穷举的思路:回溯!的确也是这样,上面代码中的例子中的物品只由5个,换句话说,所构成的二叉树也只有5层!但当物品有10个,20个,甚至100个话,构成的二叉树是多么的庞大!(笔者觉得不可想象深度为100的二叉树)!故在上述代码的基础上,很有必要去做一个算法的优化!在网上看了别人的博客之后,大致知道了一个思路:即在不断的遍历左子树(即不断的将物品装入)的过程中,如果出现了不能再装入下一物品的情况时,这时就需要去遍历右子树!但是如果,如果在剩余容量情况下,将剩余容量的背包装满(如果大于0且小于物品的重量的话,按照单位重量的价值乘以剩余容量来算)的情况下 得到的总价值比当前最优解还要的小的话,那么该右子树是完全没有必要去遍历,而需要直接剪除的!这样就达到了优化的目的!废话不多说,直接上代码:


/**

*本算法的上界函数是这样定义的!首先利用冒泡法排序,按照单位价值右高到低开始顺序排列!

*这样的话,就能保证,先装进去的是性价比(自己发明的,勿喷)最优的!而在上界函数中,也是如此,求的是剩余

* 容量在右子树所能容纳的最高价值(背包容量允许情况下),一旦小于当前最优解,那么就

* 没有继续遍历该右子树的必要

*

*/

public class Demo2 {private static int n;// 物品数量private static double c;// 背包容量private static double[] v = new double[100];// 各个物品的价值private static double[] w = new double[100];// 各个物品的重量private static double cw = 0.0;// 当前背包重量private static double cp = 0.0;// 当前背包中物品价值private static double bestp = 0.0;// 当前最优价值private static double[] perp = new double[100];// 单位物品价值排序后private static int order[] = new int[100];// 物品编号private static int[] put = new int[100];// 设置是否装入// 按单位价值排序private static void knapsack() {int i, j;int temporder = 0;double temp = 0.0;for (i = 1; i <= n; i++)perp[i] = v[i] / w[i];for (i = 1; i <= n - 1; i++) {for (j = i + 1; j <= n; j++)if (perp[i] < perp[j])// 冒泡排序perp[],order[],sortv[],sortw[]{temp = perp[i];perp[i] = perp[i];perp[j] = temp;temporder = order[i];order[i] = order[j];order[j] = temporder;temp = v[i];v[i] = v[j];v[j] = temp;temp = w[i];w[i] = w[j];w[j] = temp;}}}// 回溯函数private static void backtrack(int i) {if (i > n) {bestp = cp;return;}//将物品装进背包:此种情况的话if (cw + w[i] <= c) {cw += w[i];cp += v[i];put[i] = 1;backtrack(i + 1);cw -= w[i];cp -= v[i];}if (bound(i + 1) > bestp)// 符合条件搜索右子数backtrack(i + 1);}// 计算上界函数

//算法剩余容量的情况下最多能装的价值private static double bound(int i) {double leftw = c - cw;double b = cp;while (i <= n && w[i] <= leftw) {leftw -= w[i];b += v[i];i++;}if (i <= n)b += v[i] / w[i] * leftw;return b;}public static void main(String[] args) {// private static int[] w= {0,2,2,6,5,5}; //每个物体的重量:5个物体// private static int[] v = {0,6,3,5,4,6}; //每个物体的价值v[0] = 0;v[1] = 6;v[2] = 3;v[3] = 5;v[4] = 4;v[5] = 6;w[0] = 0;w[1] = 2;w[2] = 2;w[3] = 6;w[4] = 5;w[5] = 5;n = 5;c = 10;knapsack();backtrack(1);System.out.println(bestp);for (int i = 1; i <= n; i++) {System.out.print(put[i] + " ");}System.out.println("");}}

现在,我们来分析下时间复杂度,最好的情况当然是能够剪除所有的右子树,而最优的物品选择刚好全在左子树上了!而最坏的情况自然是搜索右子树的次数最多了呀!
 到此,直接遍历穷举和优化之后的回溯法来解决10背包问题的分析解答终于写完了!写了一上午啊!
 笔者水平有限,望各位看官勿怪!

参考博客:优化的回溯法解决10背包问题
               遍历的回溯法解决10背包问题
0 0
原创粉丝点击