动态规划的学习之旅(一)

前言

最近在刷算法题的时候发现很多题都与动态规划(Dynamic Programming)有关系,回想当初动态规划(Dynamic Programming)初学时也会感到有些难度,直到现在有些动态规划(Dynamic Programming)的题也会令我感到无从下手,此篇博客既是对我自己的知识回顾,也可作为初学入门。本人水平有限,如有错误,敬请斧正。

什么叫动态规划
当然对于概念性的东西都是枯燥无味的,我也一样,百度解释(由于篇幅就直接给出链接):什么是动态规划(Dynamic Programming)

别急我这里还有比较好理解一个,这是我偶然看到的一个一篇文章可以算是我的入门教程了:漫画:什么是动态规划?(Dynamic Programming)

想必看了以上两个解释大家应该都对动态规划有了一定的认识,接下来就是干货了

动态规划解题步骤
首先解动态规划Dynamic Programming)的题一般有自顶向下和自底向上两种求解方式(其实可以将其求解过程和高中的递推类比),举一个比较简单的例子,刚学编程的人都接触过斐波那契数列吧,它有如下递推式:

Fibonacci (1)=1
Fibonacci (2)=1
Fibonacci (3)=Fibonacci (2)+ Fibonacci (1)
……
Fibonacci (n)=Fibonacci (n-1)+ Fibonacci (n-2)

我们最开始处理这个问题时常常用递归进行处理,但是却忽视了一个问题,在不断递归的过程中会产生重复计算,导致空间开销变大,下图可以较为清晰地看出这个问题
递归计算图 所以我们应该想到对多余计算的节点进行存储,以便下次使用,这就是所谓的备忘录法,接下来就是对递归边界进行描述,Fibonacci (1)=1,Fibonacci (2)=1。同时我们得到了一个递推式Fibonacci (n)=Fibonacci (n-1)+ Fibonacci (n-2),在动态规划中我们将其称为状态转移方程,而动态规划的难点也就在于此,只要能找到正确的状态转移方程变可以正确求解。以下为此题代码

#include<iostream>
using namespace std;

int Fibonacci (int i,int *meo)
{
	if(meo[i]>0)
		return meo[i];
		
	if(i==1||i==2)
		return (meo[i]=1);
	
	return (meo[i]=Fibonacci(i-1,meo)+Fibonacci(i-2,meo));
} 

int main()
{
	int meo[100]={0};
	for(int i=1;i<10;i++)
	{
		cout<<(Fibonacci (i,meo))<<' ';
		cout<<meo[i]<<endl;
	}
	
}

当然到现在为止还很基础,但是接下来就是动态规划常见的几种情景:

背包问题

  1. 0/1背包
    这个是学习背包问题的人一开始就会接触到的问题,例题如下:
    在这里插入图片描述解析:首先拿到这种问题,我们首先得要会找到状态转移方程,由题目我们可以得知对于N件物品,每一件都有两种选择,放入背包或者不放入背包,由此得到状态转移方程为前i组物品获得价值=max(前i-1组+i个物品价值,前i-1组不加i个物品价值)
    自顶向下
int pacl(int i,int *f,int j,int* w,int *v )
{
	if(i==0||j==0)
		return f[i][j]=0;
		 
	if(f[i][j]>-1)
		return f[i][j];
		
	return (f[i][j]=max(pacl(i-1,f,j-w[i],v)+v[i],pacl(i-1,f,j,v)));
}

注:其中f[i][j]是前i个物品容量为j时的最大价值.
自底向上:

自底向上进行求解可以减少空间开销所以推荐大家尽量使用这种方法求解,自顶向下助于理解,但是不是最好的解法。
代码如下:

#include<iostream>
#include<algorithm>
int f[1000][1000];
int w[100],v[1000];
using namespace std;
int main()
{
	int n,weight;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>w[i]>>v[i];
	for(int i=1;i<1000;i++)
		f[0][i]=f[i][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=weight;j++)
		{
			if(w[i]>j)
				f[i][j]=f[i-1][j];//放不下 
			else
				f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);//能放下 
		}
		
} 
		

此代码可以再进行优化,将二维数组压缩为一维,这里由于篇幅原因不作赘述。

  1. 完全背包
    完全背包与0/1背包类似,不同在于完全背包所有物品数量没有上限,可任意取,所以只要对0/1背包每一个进行从零到一枚举取得个数并找出最大值即可,代码如下:
	for(int i=1;i<=n;i++)
		for(int j=1;j<=weight;j++)
			for(int k=0;k*w[i]<=j;k++) 
		 	{
				f[i][j]=max(f[i][j],f[i-1][j-k*w[i]]+k*v[i]); 
			}
  1. 多重背包
    多重背包也与上面两个类似只是数量上有了限制,每个物品都有自己的上限,所以只要根据完全背包进行适当改进即可。
for(int i=1;i<=n;i++)
		for(int j=1;j<=weight;j++)
			for(int k=0;k*w[i]<=j&&k<=num[i];k++) 
			{
				f[i][j]=max(f[i][j],f[i-1][j-k*w[i]]+k*v[i]);
			}
		
  1. 分组背包,混合背包,依赖背包

(1)分组背包其实只是有多种物品每种物品分为一组,每组只能取一个或者不取
(2)混合背包则是将0/1背包,完全背包,多重背包进行混合罢了,在求解时,只需要对每一个物品进行判断看其属于哪一类再进行求解即可
(3)对于依赖背包我这里有一个比较好的例子供大家参考
在这里插入图片描述
这个题中我们应看到每个物品都有其附属品,所以我们有这几种方案只取主件,只取一个附件,取两个附件根据每一个情况再进行细致分析。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值