【动态规划】01背包问题_01背包问题动态规划算法

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:xhs1999xhs (备注Java)
img

正文

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

🏡个人主页:starry陆离

🕒首发日期:2022年5月16日星期一

🌌上期文章:【动态规划】最长上升子序列

📚订阅专栏:算法分析与设计
如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦

在这里插入图片描述

【背包问题】01背包问题

1. 问题描述

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

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

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

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

image-20220514180709413

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个)

image-20220514181545409

2.2 推导递归式

判断是否放入第i件?

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

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

结合两式即可得:

image-20220514202617064

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 图解算法

幻灯片1

幻灯片2

幻灯片3

幻灯片4

幻灯片5

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

幻灯片6

5 代码实现

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

空间复杂度:S(nc)

private static int DpSolve(int n, int c, int[] w, int[] v, int[][] dp) {
		//初始化第n行
		for(int j=0;j<=c;++j) {
			if(j>=w[n]) {
				//System.out.println("w[n]="+w[n]);
				dp[n][j]=v[n];
			}
		}
		
		//动态规划
		for(int i=n-1;i>=1;i--) {
			for(int j=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]);
				}
			}
		}
		return dp[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

幻灯片7

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

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

幻灯片8

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

幻灯片9

幻灯片10

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

幻灯片11

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


import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int n;
		int c;
		int[] w,v;
		int[][] dp;
		while(scanner.hasNext()) {
			n=scanner.nextInt();
			c=scanner.nextInt();
			w=new int[n+1];
			v=new int[n+1];
			dp=new int[n+1][c+1];
			for(int i=1;i<=n;++i) {
				v[i]=scanner.nextInt();
			}
			for(int i=1;i<=n;++i) {
				w[i]=scanner.nextInt();
			}
			
			for(int j=0;j<=c;++j) {
				if(j>=w[n]) {
					
					dp[n][j]=v[n];
				}
			}
			
			//动态规划
			for(int i=n-1;i>=1;i--) {
				for(int j=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]);
					}
				}
			}
			
			int ans=dp[1][c];		
			System.out.println(ans);	
			Gouzao(n,c,w,v,dp);
		}

	}

	private static void Gouzao(int n, int c, int[] w, int[] v, int[][] dp) {
		
		for(int i=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];
			}
		}
		int last=(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从大往下变化

image-20220514202617064

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

最后我们该如何学习?

1、看视频进行系统学习

这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Mybatis 3源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Redis学习笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Spring Boot核心技术-笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:xhs1999xhs (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img-hamxIa4V-1713687501431)]

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

[外链图片转存中…(img-MVCykfYP-1713687501431)]

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:xhs1999xhs (备注Java)
[外链图片转存中…(img-nIu5HzWJ-1713687501432)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
01背包问题一个经典的动态规划问题,它的目是在给定一组物品和一个包容量的情况下,选择一些品放入背包中,使得放入背包的物品总价最大,同时不能超过背包的容量。 动态规划算法是解决01背包问题的常用方法。下面是该算法的实现步骤: 1. 创建一个二维数组dp,其中dp[i][j]表示在前i个物品中选择一些放入容量为j的背包中所能获得的最大价值。 2. 初始化dp数组的第一行和第一列为0,表示背包容量为0或者没有物品可选时,最大价值都为0。 3. 对于每个物品i,遍历背包容量j从1到背包总容量: - 如果当前物品i的重量大于背包容量j,则无法将物品i放入背包中,此时最大价值为dp[i-1][j]。 - 如果当前物品i的重量小于等于背包容量j,则可以选择将物品i放入背包中或者不放入背包中: - 如果选择放入物品i,则最大价值为dp[i-1][j-w[i]] + v[i],其中w[i]表示物品i的重量,v[i]表示物品i的价值。 - 如果选择不放入物品i,则最大价值为dp[i-1][j]。 - 取上述两种情况的最大值作为dp[i][j]的值。 4. 遍历完所有物品后,dp[n][C]即为问题的解,其中n表示物品的个数,C表示背包的总容量。 下面是01背包问题动态规划算法的实现代码: ```python def knapsack(W, wt, val, n): dp = [[0 for _ in range(W+1)] for _ in range(n+1)] for i in range(1, n+1): for j in range(1, W+1): if wt[i-1] > j: dp[i][j] = dp[i-1][j] else: dp[i][j] = max(dp[i-1][j], dp[i-1][j-wt[i-1]] + val[i-1]) return dp[n][W] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值