背包问题总结分析

背包问题是个很经典的动态规划问题,本博客对背包问题及其常见变种的解法和思路进行总结分析

01背包

问题介绍

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 v[i],价值是 w[i]。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

基本思路

定义int[][] dp,dp[i][j] 表示当容量为j时,对于前i个物品而言的最优放置策略(即最大价值)。对于物品 i 而言,只有放与不放,这两种选择。因此可以得到 状态转移方程:

放物品 i :dp[i][j] = dp[i - 1][j - v[i]] + w[i];

不放物品 i :dp[i][j] = dp[i - 1][j]。

直观方法:

// v和w数组长度都是 N + 1,v[0]和w[0]都是0

private static void backpack1(int N, int V, int[] v, int[] w) {

int[][] dp = new int[N + 1][V + 1];

for (int i = 1; i <= N; ++i) {

for (int j = 1; j <= V; ++j) {

dp[i][j] = dp[i - 1][j];

if (j >= v[i]) {

dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);

}

}

}

System.out.println(dp[N][V]);

}

这种方法空间不是最优的。观察代码发现,dp[i]只跟dp[i-1]有关,所以可以将二维降成一维。

优化方法:

private static void backpack2(int N, int V, int[] v, int[] w) {

int[] dp = new int[V + 1];

for (int i = 1; i <= N; ++i) {

for (int j = V; j >= v[i]; --j) {

dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);

}

}

System.out.println(dp[V]);

}

注意:

内层循环不能顺序枚举。dp[j - v[i]]实际上相当于 dp[i - 1][j - v[i]],而不是dp[i][j - v[i]],如果顺序枚举, dp[i] 的 j – v[i] 的位置已经被计算过,覆盖了。所以应该通过倒序枚举来规避这个问题。

两个要点:

若 dp[] 全部初始化为0,计算结果的 dp[V] 就是答案;

若 dp[0] 初始化为0,其它元素全部初始化为负无穷,则最后遍历dp[]得到最大值为答案。

解释如下:

dp[V] 一定是最大值。同样遍历了所有物品情况下,容量 V 大于 V – X ,最后得到的价值 dp[V] 必然大于 dp[V – X]。

dp数组初始化值全为 0 ,则允许dp[V]从任何一个初始项转化而来,并不一定是 dp[0]。最终结果如果从 dp[k] 转化而来,说明有 k 体积的空余。但是,如果我们更改一下dp数组初始化的情况:

将 dp[0][0] 取0 ,dp[0][1] ~ dp[0][V]全部取负无穷,同样计算,得到的结果 dp[N][1] ~ dp[N][V] 中最后一位数不一定是最大值。循环求MAX,可排除掉从“负无穷”初始值转化而来的结果。假设得到的结果 dp[N][Y] ,则该值为体积总和恰好等于 Y 的最大价值。

完全背包

问题介绍

与01背包的区别:所有物品可以无限件使用。其它都一样。

基本思路

跟01背包一样,一定需要一个for (int i = 1; i <= N; ++i)外层循环,枚举每个物品。内部循环相较于01背包需要发生呢个变化。需要枚举 v[i]~V 容量下,放置 1~k 个物品i,最大价值的情况,并记录进 dp 数组。因此直观思路是再套两层循环,如下所示。

for (int j = V; j >= v[i]; --j) {

for(int k=1;k*v[i]<=j;++k){

dp[j] = Math.max(dp[j], dp[j - k * v[i]] + w[i]);

}

}

实际上, k 的那一层循环是可以省略的。如下所示

完全背包解法:

private static void completeBackpack(int N, int V, int[] v, int[] w) {

int[] dp = new int[V + 1];

for (int i = 1; i <= N; ++i) {

for (int j = v[i]; j <= V; ++j) {

dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);

}

}

System.out.println(dp[V]);

}

如上述代码所示,内层遍历 j 采用正向枚举即可节省一层循环。前文提到过,在01背包里,这样枚举是错误的,因为dp[i][] 会把 dp[i-1][] 覆盖掉。但在本问题中可以巧妙利用其“覆盖”的特性,缩减时间复杂度。覆盖的过程,实际上就是原有的 dp 值加一个 w[i] 。对于每一个 dp[j] 而言,需要考虑是在 dp[j – v[i]] 加一个物品 i 的价值,还是不加物品 i 继续沿用 dp[j] 。for (int j = v[i]; j <= V; ++j)这样循环,最多可以加 (V – v[i] + 1)次物品,由于物品 i 体积大于等于 1,所以物品 i 的添加次数不可能超过 (V – v[i])/ 1 次,所以一定会遇到最优的情况。

涉及顺序的完全背包问题

即放入背包中的物品,顺序不同的序列被视为不同的组合,求满足target的总组合数。

例题:单词拆分,组合总和IV

思路

将前面完全背包问题解决方案中两层循环倒过来即可解决该问题,即把对容量的遍历放在外层,物品的循环放在内层。前文的循环方式相当于去除了重复的组合。

换种思路来理解:假设物品1~ n,对于每一个容量K而言(K<=target),要从前一步抵达K的位置,有1~ n种可能。假设某物品体积为v,对于容量(K-v)而言也同样是遍历过n个物品,所以应该在内层循环遍历n个物品,这样一定枚举了所有排列情况。

示例代码如下:

class Solution {

public int combinationSum4(int[] nums, int target) {

int[] dp = new int[target+1];

dp[0] = 1;

for(int j=1;j<=target;++j){

for(int item : nums){

if(j>=item) dp[j] += dp[j-item];

}

}

return dp[target];

}

}

多重背包

问题介绍

在完全背包基础上,对每个物品限定数量。

普通解法

import java.util.Scanner;

public class Main{

public static void main(String[] args) throws Exception{

Scanner reader = new Scanner(System.in);

int N = reader.nextInt();

int V = reader.nextInt();

int[] dp = new int[V + 1];

for(int i=1;i<=N;++i){

int v = reader.nextInt();

int w = reader.nextInt();

int s = reader.nextInt();

for(int j=V;j>=v;--j){

for(int k=1;k<=s&&k*v<=j;++k){

dp[j] = Math.max(dp[j],dp[j-k*v]+k*w);

}

}

}

System.out.println(dp[V]);

}

}

二进制优化方法

实际上,当s非常大时,将物品划分为s个物品,转化为01背包问题来计算,这样时间复杂度非常巨大。有一个技巧,可以简化该问题:对于任意一个数S,分成数量不同的若干个数,这些数选或不选可以拼成小于S的任意一个数。

如何划分这个S便是问题的关键。试想,对于一个数 7 它的二进制形式是 111 ,每一位上取 1 或者取 0 正好可以描述“选物品”或者“不选物品”两个行为,因此可以想到将 7 划分为 1 + 2 + 4。对于二进制位全为 1 的数,可以使用上述方法进行划分。如果不是这样的数,譬如说10,该如何划分呢?

实际上可以划分为 1 + 2 + 4 + 3。要证明此猜想,只需要证明7~10之间的数一定能通过1、2、4、3这四个数选或不选来得到即可。由于 1、2、4 一定能得到5、6、7,因此 +3 一定能得到 8、9、10,所以得证。

二进制优化方法的代码如下所示:

import java.util.Scanner;

import java.util.LinkedList;

import java.util.List;

public class Main{

public static void main(String[] args) throws Exception {

Scanner reader = new Scanner(System.in);

int N = reader.nextInt();

int V = reader.nextInt();

List vList = new LinkedList<>();

List wList = new LinkedList<>();

int[] dp = new int[V + 1];

for (int i = 0; i < N; ++i) {

int v = reader.nextInt();

int w = reader.nextInt();

int s = reader.nextInt();

for (int k = 1; k <= s; k *= 2) {

vList.add(k * v);

wList.add(k * w);

s -= k;

}

if (s > 0) {

vList.add(s * v);

wList.add(s * w);

}

}

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

int v = vList.get(i);

int w = wList.get(i);

for (int j = V; j >= v; --j) {

dp[j] = Math.max(dp[j], dp[j - v] + w);

}

}

System.out.println(dp[V]);

}

}

混合背包问题

描述:物品一共有三类,第一类物品只能用一次(01背包),第二类物品能用无限次(完全背包),第三类物品最多用s次(多重背包)

思路

将01背包、完全背包、二进制优化的多重背包三个算法都结合起来,遍历到每个物品的时候做一个判断即可。

遍历每一行输入,即每一类物品;

如果是物品只能选一次,按照01背包方法,更新dp数组(计算每一个容量下,选或不选的最大价值);

如果物品可以选无数次,则按照完全背包方法,更新dp数组;

如果给定 s ,则将s按二进制分解为log(s)份,也按照01背包来计算。

具体的题目描述可参考混合背包问题,代码如下:

import java.util.Scanner;

public class Main{

public static void main(String[] args) throws Exception {

Scanner reader = new Scanner(System.in);

int N = reader.nextInt();

int V = reader.nextInt();

int[] dp = new int[V + 1];

for(int i=0;i

int v = reader.nextInt();

int w = reader.nextInt();

int s = reader.nextInt();

if(s == -1){// 01背包

dp_01(dp, V, v, w);

}else if(s == 0){ // 完全背包

for(int j=v;j<=V;++j){

dp[j] = Math.max(dp[j],dp[j-v]+w);

}

}else{ // 多重背包

for(int k=1;k<=s;s-=k,k*=2){

dp_01(dp, V, k*v, k*w);

}

if(s>0) dp_01(dp, V, s*v, s*w);

}

}

System.out.println(dp[V]);

}

private static void dp_01(int[] dp, int V, int v, int w){

for(int j=V;j>=v;--j){

dp[j] = Math.max(dp[j],dp[j-v]+w);

}

}

}

二维费用背包问题

每个物品有两个属性:体积和重量。在01背包的基础上,多加入了一个维度“重量”,即费用从一维扩展到二维。

思路

将dp数组设置为二维数组,分别代表体积和重量两个维度,跟01背包相比多了一层循环。代码如下:

import java.util.Scanner;

public class Main{

public static void main(String[] args){

Scanner reader = new Scanner(System.in);

int N = reader.nextInt();//物品数量

int V = reader.nextInt();//体积上限

int M = reader.nextInt();//重量上限

int[][] dp = new int[V+1][M+1];

for(int i=0;i

int v = reader.nextInt();//物品体积

int m = reader.nextInt();//物品重量

int w = reader.nextInt();//物品价值

for(int j=V;j>=v;--j){

for(int k=M;k>=m;--k){

dp[j][k] = Math.max(dp[j][k],dp[j-v][k-m]+w);

}

}

}

System.out.println(dp[V][M]);

}

}

分组背包问题

输入物品有 N 个组,每一组中只能选择一个物品。

思路

依然是在01背包的基础上做改动。每次选择时,假设组内有S个物品,则有S+1种决策,遍历这些决策,选取价值最大的即可。代码如下所示:

import java.util.Scanner;

public class Main{

public static void main(String[] args){

Scanner reader = new Scanner(System.in);

int N = reader.nextInt();

int V = reader.nextInt();

int[] dp=new int[V+1];

for(int i=0;i

int s = reader.nextInt();

int[] v = new int[s];

int[] w = new int[s];

for(int k=0;k

v[k] = reader.nextInt();

w[k] = reader.nextInt();

}

for(int j=V;j>0;--j){

for(int k=0;k

if(j>=v[k])

dp[j] = Math.max(dp[j],dp[j-v[k]]+w[k]);

}

}

}

System.out.println(dp[V]);

}

}

程序员灯塔

转载请注明原文链接:【数据结构与算法】背包问题总结梳理

数据结构背包问题c语言思路,【数据结构与算法】背包问题总结梳理相关推荐

  1. 给定C语言数据结构,给定C语言的数据结构struct T { int w; union T { char c;int i;double d;)U; };...

    给定C语言的数据结构struct T { int w: union T { char c:int i:double d:)U: }: 更多相关问题 HBV感染者的血清用电镜观察可见到_________ ...

  2. 数据结构背包问题c语言思路,C语言学习趣事_数据结构_经典命题_1_背包问题_分析_1...

    /*1.问题描述 假设有一个能装入总体积为T的背包和n件体积分别为w1,w2,-wn的物品, 能否从n件物品中挑选若干件恰好装满背包,即使w1+w2+-+wm=T, 要求找出所有满足上述条件的解. 例 ...

  3. python算法描述_数据结构(Python语言描述)- 排序算法

    目录 1.基本排序算法 1.1 选择排序 1.2 冒泡排序 1.3 插入排序 2. 更快排序算法 2.1 快速排序 2.2 归并排序(合并排序) 1.基本排序算法 计算机科学家设计了很多巧妙的策略对列 ...

  4. 考研专业课c语言与数据结构,南开大学816 C语言与数据结构2018考研专业课大纲...

    南开大学816 C语言与数据结构2018考研专业课大纲 2017-09-29 14:41 | 考研集训营 南开大学2018考研专业课大纲已经公布,每个学校自专业课考试大纲及专业课考试内容不一样,小编会 ...

  5. 13:Scala语言的数据结构和算法

    第十九章 Scala语言的数据结构和算法 19.1 数据结构(算法)的介绍 数据结构的介绍   1.数据结构是一门研究算法的学科,只从有了编程语言也就有了数据结构.学好数据结构可以编写出更加漂亮.更加 ...

  6. 大数据技术之_16_Scala学习_13_Scala语言的数据结构和算法_Scala学习之旅收官之作

    大数据技术之_16_Scala学习_13 第十九章 Scala语言的数据结构和算法 19.1 数据结构(算法)的介绍 19.2 看几个实际编程中遇到的问题 19.2.1 一个五子棋程序 19.2.2 ...

  7. 算法和数据结构(Java语言)

    算法和数据结构(Java语言) 持续更新中- 线性结构和非线性结构 线性结构 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系 线性结构有两种不同的存储结构,即顺序存储结构和链式 ...

  8. 算法和数据结构(golang语言实现)

    算法和数据结构(golang语言实现) 第1节 选择.冒泡.插入.复杂度 选择排序 选择排序 时间复杂度为O(N^2) 额外空间复杂度O(1) 过程: arr[0-N-1]范围上,找到最小值所在的位置 ...

  9. 七桥问题c语言程序数据结构,数据结构与算法学习——图论

    什么是图? 在计算机程序设计中,图结构也是一种非常常见的数据结构 但是图论其实是一个非常大的话题 图结构是一种与树结构有些相似的数据结构 图论是数学的一个分支,并且在数学概念上,树是图的一种 它以图为 ...

最新文章

  1. alert()的功能_前端实现简单的图片上传小图预览功能
  2. Java 序列化的一些简 单总结
  3. 较真的来了!这篇【硬核论文】为何恺明新作MAE提供了一种理论解释和数学证明...
  4. iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule
  5. oracle除了什么之外,Oracle翻译
  6. 第十一周学习进度报告
  7. 关于android LinearLayout的比例布局(转载)
  8. mtk+android+wear,MTK专用处理器--可穿戴
  9. 数据安全法下,企业如何平衡数据安全合规与业务性能?| 产业安全专家谈
  10. MATLAB注意事项
  11. 职场寒冬来袭,“零工经济”让你比90%的人更有安全感
  12. Wifi4更换Wifi6路由器的使用体验
  13. javaweb三大框架
  14. mysql数据库锁 栅栏,如何使用MySQL查找多边形地理围栏中包含的点
  15. 费舍尔方法(Fisher‘s method)的数学原理解释
  16. 设计方法:怎么写出优雅的 Go 项目?
  17. Git - Smart Checkout、Force Checkout 区别
  18. 一起来写个酷炫的水波纹浪啊浪界面
  19. 最近感觉公司的福利还不错
  20. 科大讯飞:我们和华为基因相似,板凳能坐十年冷,如今向C端强劲发力

热门文章

  1. 大学生规划职业宜趁早 认清专业确定方向 ——三人大学学习平台
  2. PS中按住Alt键或者Ctrl+Alt+G创建剪贴蒙版
  3. IT风投的一个典型案例--阿里巴巴
  4. python爬虫入门:在命令行搜索并下载小说
  5. 美的硅谷研发中心成立,专注人工智能推动产品智能化升级
  6. 【报告分享】2023人工智能企业研究报告:为什么是英伟达?.pdf(附下载链接)...
  7. html textarea滚动条样式设置
  8. 换种姿势, 在家办公 - 罗技MX Keys + M720主观评测
  9. python教程实例画图_Python 练习实例57
  10. python绘制折线图保存_Python系统学习 - 绘制简单折线图