质量约束

背包问题也是一道经典的动态规划问题,它有许多表述形式,什么劫匪小偷抢超市啦,出去旅行要打包啦,简单来说,背包问题给出了一序对集合 ,其中 是物品总重量, 是物品价值,要求从中选取总重量不超过 的物品,使得物品总价值 尽可能的大。

由于上述问题中有 件物品,所以将该问题定义为 ,假设 是该问题最优解的总价值,显然 的值与 和 都相关,即

现在我们稍微缩小问题的规模,若已经从物品列表中拿取了 ,并从限定总重量中减去该物品的重量 ,然后在剩下的物品 中再取总重量不超过 的物品,此时的问题用符号 表示,下标 表示排除的物品序号,于是我们便得到一系列子问题

对于问题 ,设它的最优解为集合 ,显然 ,并且总价值为

假设问题 的最优解为集合 ,下面我们将证明,必然存在某一个 使得 。

这里我们使用反证法,假设对于任意 都不存在 ,即 不是问题 的最优解,那么则有两种情况,即

1、 的总价值比 的更小,此时 将是问题 更好的解。

2、 的总重量比 更大,此时 的总重量将超过 。

也就是说上述两种情况都与 是最优解的事实相违,于是从反方向证明了前面的论断。从而我们找到了该问题的最优子结构,即 的最优解可以从它的某个子问题 的最优解得到。

下图显示了当 时的各层次问题组成的树形结构

对于任意的 n ,我们将得到一个深度为 n 的树形递归结构,它的根节点有 n 个子节点,第二层节点有 n-1 个子节点,以此类推,如果要完全搜索整棵树,那么时间复杂度将是 。

但事实上,我们不必完全求解所有子问题,举个简单的例子,假如物品 在所有物品中具有最小的重量以及最高的价值,那么首先选取该物品是最明智的,于是我们只需计算第二层节点中的问题 。另一方面,如果物品 具有最大的重量,以及最小的价值,那么我们在这轮选择中就可以不用计算 ,因为选择它显然有点得不偿失。虽然这些只是理想的情况,因为很可能不存在这样的物品,但这给我们以启发,即,可以通过物品之间的重量和价值关系来判断是否有必要在本轮选择中计算相应的子问题。

那么具体的判断规则是怎样的呢?我们可以将所有物品按价值从高到低进行排序,然后以最后一个物品 为基准向前搜索,一旦发现某个物品 的重量小于 或者大于,那么我们就排除问题 ,当然这并不是说 就被我们永久屏蔽了,而是当前有明显比它更好的选择。接着以 为基准向前搜索,直到 。

这一过程的伪码为

func remove_problem_list(items)

remove_list = [];

sort(items);

n = items.length;

for i:n -> 1

base_weight = items[i].weight;

for j:i -> 1

if items[j].weight < base_weight

remove_list.append(items[i]);

break;

end

end

end

return remove_list;

end

还是以 n = 3 的情况来举例说明,假设这三个物品的重量和价值如下所示

并且重量限制 。于是我们看到第一轮选择中, 是明显可以被排除的,这就相当于修剪掉了前面树形结构中的 分支,它的后续子问题也就自然排除。它的整个运行过程如下所示

看起来这就是对树的深度优先搜索,在每个节点都会比较它的子节点返回的价值大小,并向其父节点返回较大的那一个。写成递归过程如下

func solve(W, items)

## 处理平凡情况

if items.length == 1

if items[0].weight <= W

return items[0].value;

else

return 0;

end

end

remove_problem_list = remove_problem_list(items);

select_item_list = item not in remove_problem_list;

values = [];

for item in select_item_list

values.append(solve(W - item.weight, items except item) + item.value);

end

return max in values;

end

上述方案只解决了问题的一部分,即能够取得物品的最大总价值,却没有给出该选择哪些物品。当然这也很好办,只需要让递归函数返回最优的路径即可,该路径就是已经选择的物品列表。伪码如下

func solve(W, items)

## 处理平凡情况

if items.length == 1

if items[0].weight <= W

return items;

else

return [];

end

end

remove_problem_list = remove_problem_list(items);

select_item_list = items not in remove_problem_list;

candicate_items_list = [];

for item in select_item_list

candicate_items_list.append(solve(W - item.weight, items except item).append(item));

end

return candicate_items in candicate_items_list which have max value

end

下面我们用 Java 实现来做一个具体的例子,原理和上面的伪码差不多

//定义物品类 Item

class Item{

int w;

int v;

Item(int w, int v) {

this.w = w;

this.v = v;

}

public String toString(){

return w+":" +v;

}

}

//移除不必要问题的方法

private List removeProblemList(List items, int W) {

ArrayList remove = new ArrayList<>();

for(int i = items.size() - 1; i >= 0; i--) {

Item base = items.get(i);

if(base.w > W) {

remove.add(base);

}else{

for(int j = i - 1; j >= 0; j--) {

if(base.w > items.get(j).w) {

remove.add(base);

break;

}

}

}

}

return remove;

}

//递归调用方法

public List solve(int W, List items) {

if(W <= 0) {

return new ArrayList<>();

}

if(items.size() == 1) {

if(items.get(0).w <= W) {

ArrayList candicate = new ArrayList<>();

candicate.add(items.get(0));

return candicate;

}else{

return new ArrayList<>();

}

}

List remove = removeProblemList(items, W);

//待求解问题

List selects = items.stream()

.filter(item -> !remove.contains(item))

.collect(Collectors.toList());

ArrayList> candicate_items_list = new ArrayList<>();

for (Item select : selects) {

List remain = items.stream().filter(item -> item != select).collect(Collectors.toList());

List result = solve(W - select.w, remain);

result.add(select);

candicate_items_list.add(result);

}

//从候选列表中找到具有最大价值的物品路径

int max = 0;

int max_index=0;

for (int i = 0; i < candicate_items_list.size(); i++) {

int value = candicate_items_list.get(i).stream().mapToInt(item -> item.v).sum();

if(value > max){

max = value;

max_index = i;

}

}

return candicate_items_list.get(max_index);

}

测试:

@Test

public void testKP() {

Item[] items = {new Item(3,4),new Item(3,4),new Item(3,4),

new Item(8, 5),new Item(8, 5),new Item(2, 2),new Item(2, 2),

new Item(5, 3),new Item(20, 11)};

sort(items);

int W = 60;

List solution = solve(W, Arrays.asList(items));

System.out.println(solution);

//output: [2:2, 2:2, 5:3, 3:4, 3:4, 3:4, 8:5, 8:5, 20:11]

}

体积约束的讨论

背包问题有很多变种,比如除了质量限制外,我们还可以限制体积,也就是说给每个物品再增加一个体积属性,然后在质量和体积不超过预设的条件下,挑选价值尽可能大的物品组合。

其实增加一个约束并不会对我们要求解问题的树形结构产生根本性改变,只不过是在每个子问题中增加了一个类似对质量的处理步骤。让我们回顾一下,我们是怎样处理质量的,在问题 中, 首先从 中挑选出 ,然后从约束质量 中减去 ,便获得子问题 。

现在我们增加体积约束,这时的问题变成 ,其中 表示约束体积,当我们选出物品 时,子问题不仅要减去质量 ,还应该减去 ,即物品 的体积。

然后再来看一下子问题修剪的方法,前面我们在挑选子问题时排除了质量和价值均不占优的物品,也就是那些质量偏大,价值偏小的物品。那么对于体积也应该同样处理,只不过这应该和质量配合起来,也就是说,质量、体积和价值均不占优势的物品才应该被排除,只要有一个属性有优势,就不应该排除。至于为何应该如此,我觉得可以通过一个简单的例子来说明,假如有两个物品,其质量、体积和价值分别为 和 ,定义约束为 ,那么此时应该选质量和体积都不占优的第一个物品。

变种 背包问题_算法题:背包问题相关推荐

  1. java蛮力法背包问题_[算法课]五种蛮力法解决01背包问题

    文章目录 注明:题目要求只能使用蛮力法 算法标签:全排列,枚举,二进制,dfs,数组 题目简介 思路 AC代码 方法一:字符串蛮力 方法二:二进制枚举 方法三:DFS 三.2闫老板思考角度 方法四:全 ...

  2. pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树

    (给算法爱好者加星标,修炼编程内功) 来源: 数据结构和算法-山大王wld 问题描述 今天我们就不做关于双指针的了,我们爬到树上玩会儿,做一道关于二叉树的题.今天的题就一句话,根据一棵树的前序遍历与中 ...

  3. 100个灯泡python编程_算法题:一个圆环上有100个灯泡,灯泡有打...

    算法题:一个圆环上有100个灯泡,灯泡有打开关闭两种状态,灯泡状态随机,按一个灯泡,相邻两个灯泡的状态也会改变. eg: ' 暗 - 亮 - 暗 ' 按中间灯泡 ,变化为 ' 亮 - 暗 - 亮 '. ...

  4. java编程贪心算法背包问题,贪心算法----部分背包问题(java实现)

    部分背包问题 给定 n 种物品和一个背包.物品 i 的重量是 Wi,其价值为 Vi,背包的容量为 C.在选择物品 i 装入背包时,可以选择物品 i 的一部分,1<= i <=n.问应如何选 ...

  5. java 简单背包问题_简单的背包问题--java递归实现

    1.主程序 package recursion; //简单背包问题-递归实现- //将不同重量的数据项放入背包中,以使背包的最后 //-----------达到指定的总重量------------- ...

  6. java中printarray和selectsort方法_算法题(一)

    目录 1 左神部分集锦 2 Leetcode前150题 3 牛客网剑指offer 4 JavaG 5 题目中的细节处理 1 左神部分集锦 1.1 Code01_FindNumber_B_In_A 在有 ...

  7. 判断字符为空_算法题:字符串转换整数 (atoi)

    题目描述 题解 分析 他人更优解 一.题目描述 二.题解 import math class Solution:def myAtoi(self,str):str = str.strip() #去除字符 ...

  8. 变种 背包问题_【朝夕的ACM笔记】动态规划-背包问题

    [朝夕的ACM笔记]目录与索引 背包问题 一.0/1背包 1.1 问题描述 有 件物品和一个大小为 的背包,以及若干物品,每种物品只有一件,大小分别为 ,其价值分别为 .问题:将哪些物品装入背包,可使 ...

  9. 使用ga算法解决背包问题_我如何使用算法解决现实生活中的手提背包的背包问题

    使用ga算法解决背包问题 I'm a nomad and live out of one carry-on bag. This means that the total weight of all m ...

最新文章

  1. 模拟PLC 的圆弧插补方式在VC中绘制圆弧
  2. Flask项目常见面试问题
  3. 从 Nginx、Apache 工作原理看为什么 Nginx 比 Apache 高效!
  4. python flask快速入门与进阶 百度云_Python Flask快速入门与进阶
  5. .NET Framework 工具
  6. 【学习笔记】第三章——内存 II(分页存储、快表与局部性原理、两级页表)
  7. aps后缀是什么文件_今日份知识分享:什么是源文件?
  8. 干货收藏!史上最强 Tomcat 8 性能优化来啦!| 原力计划
  9. 小型公司 --- OSPF 不连续区域进行通信配置
  10. Net系列框架-Dapper+AutoFac 基于接口
  11. 新stem编程scratch3水果钢琴创意键盘兼容MakeyMakey开源国产盒装使用教程
  12. WPF 框架prism代码笔记
  13. 网吧服务器发消息,网吧盗号常见途径总结以及解决办法
  14. 谈谈时间管理--陶哲轩
  15. JNPF 3.1升3.2新版本内,拉姆达表达式内时间比较
  16. 史上最全的Windows进程详解!
  17. Android逆向之旅—Hook神器Cydia Substrate使用详解
  18. java面试宝典2017
  19. Linux 命令积累(当作笔记)
  20. Python | 凸多边形间重叠面积计算

热门文章

  1. 计算机网络英语陈伟鸿,舒婷、陈伟鸿“同台”为母校厦门一中110岁庆生
  2. 华为p9 Android6 备份,华为手机怎么备份?华为手机备份数据教程
  3. 实现支付宝全额退款和部分退款功能
  4. Linux系统安装中文字体
  5. 使用几何光学实现空间相对定位(python+opencv)
  6. 读《计算机网络》——深入浅出——以考研为目标学技术面试知识二刷计网——网络层
  7. html怎么把文字居中6,html5怎么把文字居中
  8. HTTP 代理原理及实现(一)
  9. mac工具-解析json
  10. echarts 地图 海南诸岛只显示上面一小块,可以看一下图例边框颜色以及背景颜色是否相同。