【背包问题】01背包问题

简介: 给定n种物品(每种物品只有一件)和一个背包:物品i的重量是wi,其价值 为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物 品的总价值最大?对于每种物品,只有两种选择:装(1)或者不装(0),不允许装物品的一部分


👨‍🎓作者简介:一位喜欢写作,计科专业大二菜鸟

🏡个人主页:starry陆离

🕒首发日期:2022年5月14日星期六

🌌上期文章:【Android】网络编程之OKHTTP与Retrofit框架

📚订阅专栏:Android基础入门如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦

@TOC


1. 问题描述

给定n种物品(每种物品只有一件)和一个背包:物品i的重量是wi,其价值 为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物 品的总价值最大?

对于每种物品,只有两种选择:装(1)或者不装(0),不允许装物品的一部分

因此有这么一个著名的公式:

找出物品选择的组合的重量总和要不大于背包的容量为C,且要找出这些组合中装入背包中物品的总价值的最大的组合

网络异常,图片无法展示
|

2. 问题分析

如果穷举这些组合,我们知道每一个物品都有选和不选,则一共有2 n(n是物品的种类);然后去除这些组合中大于背包的容量为C的组合,再找总价值最大的即为解;显然这个数量级太大了

2.1 减少规模

定义m(i,j)是背包容量为j,可选择物品为i,i+1,…,n时0-1背包问题的最优值

m(i+1,j)为可选择物品为i+1,…,n时0-1背包问题的最优值

。。。依次类推

m(n,j)为可选择物品为n时0-1背包问题的最优值,此时,规模已为1

即当可选物品为n时,背包容量为j,如果此时的背包容量能装下第n个物品,那么m(n,j)的最优值就是第n个物品的价值vn,装不下那就是0;(因为现在的可选物品只有第n个)

网络异常,图片无法展示
|

2.2 推导递归式

判断是否放入第i件?

1)不放,背包当前产生价值仍为m(i+1,j)

2)放入,调整背包容量j-wi,背包当前产生价值为m(i+1, j-wi)+vi

结合两式即可得:

网络异常,图片无法展示
|

3 填DP表

已知n=5, c=10, w={2, 2, 6, 5, 4}, v={6, 3, 5, 4, 6}

绘制m[i,j]的最下面两行

思路:

  • 由上面的分析可知,我们是从下往上,从左往右填dp表,
  • 如m(5,10)就是表示背包容量为10时,可选择物品为n时的最优值,而我们最终的解就是m(1,10)的值
  • 首先由第一个公式填最后一行m(5,j)(0<=j<10)
  • 然后有第二个公式填剩下的表格

4 图解算法

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

剩下的两行也是用同样的方法递推,读者可自己完成,理解这个逻辑过程

网络异常,图片无法展示
|

5 代码实现

时间复杂度:O(nc)(n是物品种类,c是背包容量)

空间复杂度:S(nc)

privatestaticintDpSolve(intn, intc, int[] w, int[] v, int[][] dp) {
//初始化第n行for(intj=0;j<=c;++j) {
if(j>=w[n]) {
//System.out.println("w[n]="+w[n]);dp[n][j]=v[n];
             }
         }
//动态规划for(inti=n-1;i>=1;i--) {
for(intj=1;j<=c;++j) {
if(j<w[i]) {
dp[i][j]=dp[i+1][j];
                 }else {
dp[i][j]=Math.max(dp[i+1][j], dp[i+1][j-w[i]]+v[i]);
                 }
             }
         }
returndp[1][c];
     }

6. 构造最优解

能够求出01背包问题的最优值,我们如何求出一个最优解呢?

如上面的问题的最优解为1-》2-》5,如果用1代表选择物品,0代表不选择,那么结果可表示为11001

这个结果是如何得出的呢?

  1. 只需要当前的m(i,j)和它的下面的最优值m(i+1,j)比较,
  • 如果两者相等说明没有选择物品i,因为价值没有增加
  • 如果两者不相等说明选择了物品i,则需要把当前的背包容量j减去物品i的重量w(i),得出m(i+1,j-w(i)),其意义在于找到装入物品i前的背包能取得的最优值;
  1. 同理依次类推,循环执行第一步,直到找到倒数第二个物品是否选择
  2. 最后一步单独判断最后一个物品有没有选择,如果m(n,j)==0则表示没选择最后一个,不为0说明选择了

如上面的问题的最优解为1-》2-》5,如果用1代表选择物品,0代表不选择,那么结果可表示为11001

网络异常,图片无法展示
|

只需要当前的m(i,j)和它的下面的最优值m(i+1,j)比较,

  • 如果两者相等说明没有选择物品i,因为价值没有增加;
  • 如果两者不相等说明选择了物品i,则需要把当前的背包容量j减去物品i的重量w(i),得出m(i+1,j-w(i)),其意义在于找到装入物品i前的背包能取得的最优值;

网络异常,图片无法展示
|

同理依次类推,循环执行第一步,直到找到倒数第二个物品是否选择

网络异常,图片无法展示
|

网络异常,图片无法展示
|

最后一步单独判断最后一个物品有没有选择,如果m(n,j)==0则表示没选择最后一个,不为0说明选择了

网络异常,图片无法展示
|

privatestaticvoidGouzao(intn, intc, int[] w, int[] v, int[][] dp) {
for(inti=1;i<n;++i) {
if(dp[i][c]==dp[i+1][c]) {
//相等说明没选,输出0System.out.print("0");
             }else {
//不相等说明选择了,输出1System.out.print("1");
//更新背包的容量,要减去第i个物品的重量//准备去找装入物品i前的背包能取得的最优值c=c-w[i];
             }
         }
//处理最后一个物品intlast=(dp[n][c]>0?1:0);
System.out.println(last);
     }

7. 练习题

原题地址:https://acm.hnucm.edu.cn/JudgeOnline/

题目描述

给定n种物品和一个背包,物品i的重量是Wi,其价值为Vi,背包的容量为C。如何选择装入背包的物品,可以使得装入背包中物品的总价值最大?

输入

每组输入包括三行,第一行包括物品个数n,以及背包容量C。第二、三行包括两个一维数组,分别为每一种物品的价值和重量。

输出

输出包括两行,第一行为背包的最大总价值,第二行为所选取的物品。例如:最大总价值=15,物品选取策略为11001。数据保证答案唯一。

样例输入

5 10

6 3 5 4 6

2 2 6 5 4

样例输出

15

11001

 

importjava.util.Scanner;
publicclassMain {
publicstaticvoidmain(String[] args) {
Scannerscanner=newScanner(System.in);
intn;
intc;
int[] w,v;
int[][] dp;
while(scanner.hasNext()) {
n=scanner.nextInt();
c=scanner.nextInt();
w=newint[n+1];
v=newint[n+1];
dp=newint[n+1][c+1];
for(inti=1;i<=n;++i) {
v[i]=scanner.nextInt();
             }
for(inti=1;i<=n;++i) {
w[i]=scanner.nextInt();
             }
//初始化最后一行for(intj=0;j<=c;++j) {
if(j>=w[n]) {
dp[n][j]=v[n];
                 }
             }
//动态规划for(inti=n-1;i>=1;i--) {
for(intj=1;j<=c;++j) {
if(j<w[i]) {
dp[i][j]=dp[i+1][j];
                     }else {
dp[i][j]=Math.max(dp[i+1][j], dp[i+1][j-w[i]]+v[i]);
                     }
                 }
             }
intans=dp[1][c];       
System.out.println(ans);    
Gouzao(n,c,w,v,dp);
         }
     }
privatestaticvoidGouzao(intn, intc, int[] w, int[] v, int[][] dp) {
for(inti=1;i<n;++i) {
if(dp[i][c]==dp[i+1][c]) {
System.out.print("0");
             }else {
System.out.print("1");
c=c-w[i];
             }
         }
intlast=(dp[n][c]>0?1:0);
System.out.println(last);
     }
}

8. 扩展:空间压缩

我们注意到每次递推第i行都是用下面一行(第i+1行)的最优值,如果只用一个一维数组dp[]来存储这些值,那么

递推第i行前,dp[i]=m(i+1,j)

递推第i行后,dp[i]=m(i,j)(覆盖掉原来的值)

而在计算递推第i行时的m()数组的值,有两种选择变化:

1)不放,背包当前产生价值仍为m(i+1,j),即dp[i]保持不变

2)放入,调整背包容量j-wi,背包当前产生价值为m(i+1, j-wi)+vi,即dp[j]=dp[j-wi]+vi

但是求解dp[]数组时,必须保证dp[j-wi]还是m(i,j-wi)的值,考虑到每次递推都会覆盖掉原来dp[]数组中的值,如当j的值从小往大变化,即我们从左往右求解时dp[j-wi]已经是m(i+1,j-wi)的值了;(读者可自行推演,如果这样上一题的答案会是30)

因而我们得从右往左求解,即j从大往下变化

网络异常,图片无法展示
|

//动态规划for(inti=n;i>=1;i--) {
//j从大往下变化,保证dp[j-wi]还是m(i,j-wi)的值for(intj=c;j>=0;--j) {
if(j>=w[i]) {
dp[j]=Math.max(dp[j], dp[j-w[i]]+v[i]);
        }
    }
}


相关文章
|
2月前
|
算法 决策智能
初谈背包问题——01背包
初谈背包问题——01背包
|
7月前
01背包和完全背包
01背包和完全背包
动态规划之01背包问题和完全背包问题
动态规划之01背包问题和完全背包问题
|
算法 C语言 C++
【动态规划】背包问题(01背包,完全背包)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
128 0
动态规划——01背包问题、完全背包问题
动态规划——01背包问题、完全背包问题
94 0
动态规划:01背包问题
动态规划:01背包问题
95 0
|
算法 决策智能
背包问题——01背包|完全背包 1
背包问题——01背包|完全背包
323 0
背包问题——01背包|完全背包 2
背包问题——01背包|完全背包
198 0
|
算法 JavaScript 前端开发
动态规划-01背包
前言 动态规划和递归是一对CP,递归通过将大问题分解成小问题以求得答案,而动态规划则是通过求解小问题来组成大问题的解。虽然其本质都是先求小问题的解,但是问题的推算不同:
下一篇
DataWorks