DP的开端【 01背包问题】

本文介绍了动态规划中的01背包问题,通过斐波那契数列阐述了动态规划的重叠子问题,并提供了背包问题的递归解法。文章详细解释了动态规划数组DP的更新过程,确保在每一步都能找到背包承重的最大价值。
摘要由CSDN通过智能技术生成

背包
今天来讲讲背包问题
背包问题是DP问题的一种。
而DP问题离不开三个特性:

  1. 最优子结构
  2. 无后效性
  3. 重叠子问题

在讲背包问题之前,我们先讲讲斐波那契数列。
总所周知斐波那契数列的递推公式是
F(n)=F(n-1)+F(n-2)
但是我们可以画个图看看,在使用递归求解斐波那契数列第N项的时候会发生什么问题
在这里插入图片描述
写到这里应该就很清楚了。我们要求F(n)一定要递归求得F(n-1)和F(n-2),但是我们在求F(n-1)的时候,我还需要再递归求一个F(n-2),那么时间的开销就变大了许多许多。这就是上面DP说的重叠子问题,我重复地求了一个值,以至于当n很大很大地时候接近叶子节点的数据被计算了相当多次,这时间的开销是非常非常大的。而且我们从时间复杂度的角度出发,这个算法本身也是O(2^n)。
那么。我们从节省时间的角度我们去解决一下斐波那契数列。既然我们重复地计算了某一个值,那我们能不能用一个变量先存放他在那。直到用的时候,我才把他调度出来。也就是说,我们从n=2开始我们用for循环递进的方式,一个一个地求出前n-1项地值,那么我们在计算第n项地值得时候,我们就可以之间从数组中通过下标索引得方式去得到F(n-1)和F(n-2)从而计算出F(n)的值
这里我就不进行代码实现了。
我们回归到今天的主角:背包问题
有N件物品和一个容量为V的背包。每种物品均只有一件。 第i件物品的质量是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的质量总和不超过背包容量,且价值总和最大。
在本题中,乍一看,好像没有思路,或者有的同学会觉得性价比方案可以解决背包问题
我们一步步来仿照刚刚的斐波那契数列的O(n)求解法

第一步

我们先完成简单的输入,把缓冲区的数据全部读进一个数组中,并按照价值,升序排序
struct object {
	int val;
	int weight;
};

//这里是存储的数据类型

int main() {
	int T;
	//T代表着我有多少个物体

	while (cin >> T) {
		int weight = 0;
		cin >> weight;
		object* Arr = new object[T];
		for (int count = 0; count < T; count++) cin >> Arr[count].val >> Arr[count].weight;
		//input
		//sort
		for (int count = 0; count < T; count++) {
			for (int j = count+1; j < T; j++) {
				if (Arr[count].val > Arr[j].val) {
					//change
					object tem = Arr[count];
					Arr[count] = Arr[j];
					Arr[j] = tem;
				}
			}
		}
	}
}

这里用了冒泡排序增强可读性,我们也可以用其他更快速的排序去减少时间的复杂度,不过这不属于我们的核心代码块,而且排序不是必须的。因为每次选择我均保证最优,所以不一定要排序。
我们这里直接定义一个函数名为DP_bag
图解如下
在这里插入图片描述
于是我们便有递归实现的代码

int DP_bag(object *Arr,int Value,int weight,int last){
	
	if (last < 0)return 0;//装不下了
	else if (weight < Arr[last].weight)return DP_bag(Arr, Value, weight, last - 1);//装不下最贵的,往后找
	else return max(DP_bag(Arr, Value, weight, last - 1), Arr[last].val +DP_bag(Arr, Arr[last].val + Value, weight - Arr[last].weight, last - 1));	
}

找到最优子结构和重叠子问题后就可转换
我们先设置一个Dp数组,为了避免多次计算重叠的子问题,我们可以用一个DP数组来存储每一个值

int DP_bag(object* Arr, int T,int weight) {
	int* DP = new int[weight+1];
	memset(DP, 0, weight+1);//T3RSCG
	for (int i = 0; i < T; i++) {
		for (int j = weight; j >= Arr[i].weight; j--) {
			DP[j] = max(DP[j], DP[j - Arr[i].weight] + Arr[i].val);
		}
	}
	return DP[weight];
}

我们来解剖一下这段代码,在DP数组中,我们一开始利用memset函数将他们归0,然后是一个很难理解的嵌套式循环。
1.在这个嵌套循环中,我们先看这两个循环的判断条件。

i < T
j >= Arr[i].weight

(1)在第一层循环,很简单,我能保证每一次的i都指向当前物体,所以我们有当i<T的时候我们会退出最外层的循环,这样就能保证每一次的Arr[i]均表示当前的物体(第i个物体)
(2)在第二层循环中我们的退出条件是,当J小于Arr[i].weight我们就退出,因为Arr[i]表示当前的物体,当J小于(这里的J表示当背包最大承重为J时),如果小于表示,我已经装不下这个物体了,那我就退出
2.我们看循环内部的最优子结构

DP[j] = max(DP[j], DP[j - Arr[i].weight] + Arr[i].val);

在这个"方程"中,我们可以看到,当访问到DP[j]的时候(DP[j]的意思表示成,当背包承重为j时候所承担的最大的价值),那我此时已经知道了DP[j]前面的值(即DP[0],DP[1],DP[2]…DP[j-1])
综合来看,我们可以理解成,每输入一个物品(外层循环每跑一次),我就更新一下DP数组的值,那如何更新?
在内层循环中,我其实做的就是,在原有的DP数组的基础上,输入一个物体,看能不能装的下,能装的上,那我就比较更新当前总承重(变成总承重-该物体重量)后的值(DP[j-该物体的重量])+该物体的价值的总价值大,还是不更新的值(DP[j])大,那么更新到最后我就能保证DP[n]是最优解(由于前面的每一个承重都能保证最优解)

总结一下把,该类的背包问题其实是属于01背包问题
在以二进制为主导数制的计算机中,0往往代表着低电平,1代表着高电平。
回归到该问题,我们无非离不开判断(放/不放),也就是1 与 0之间的关系。

01背包问题,也就因此得名

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值