笨办法理解动态规划算法nullzx

如果我们解决一个问题的时候能将一个大问题转换成一个或者若干个规模较小的同等性质的问题,当我们求解出这些小问题的答案后,大问题的答案很容易解决,对于这样的情况,显然我们可以递归(或者说分治)的方式解决问题。如果在求解这些小问题的过程中发现有些小问题我们需要重复计算多次,那么我们就干脆把已经求解过的小问题的答案记录下来放在一张表中,这样下次遇到这个小问题,我们只需要查表就可以直接得到结果,这个就是动态规划的白话讲解。动态规划的难点在于如何定义问题及子问题。

1)如果可以将一个规模较大的问题转换成一个或若干个规模较小的子问题,也就是能找到递推关系,这个时候我们不妨先将程序写成递归的形式。

2)如果使用递归求解规模较小的问题上存在子问题重复求解的现象,那么我们就建立一张表(有可能这个表只有一行)记录需要重复求解的子问题。填表的过程和将大问题划分为子问题的方式相反,我们会从最简单的子问题开始填表。现在我们就利用这个套路解决下面这些经典的问题。

问题描述:菲波那契数列的定义f(n)=f(n-1)+f(n-2),且f(1)=1,f(2)=1,求f(n)的值。斐波那契数列的定义本身就是将大问题转换成两个同性质的子问题,所以我们可以直接根据定义写成递归形式。

publicstaticintrecursion(intn){if(n<0){return0;}if(n==1||n==2){return1;}returnrecursion(n-1)+recursion(n-2);}我们以f(6)为例现在把递归的过程画出来

我们发现在求解F(6)时,需要求解F(2)四次,求解F(1)三次,求解F(3)三次,F(4)两次,所以说我们的算法的效率是很低的。提高效率的办法就是将F(1),F(2),F(3)….的结果放在表中,下次要计算这些问题的时候我们直接从表中获取就好了,这就是一个最简单的动态规划的例子。现在我们按照套路,从最小的子问开始填表就好了。

publicstaticintdynamic(intn){int[]table=newint[n+1];table[1]=1;table[2]=1;/*从小到大填表*/for(inti=3;i

问题描述:总共有n个楼梯,每次可以走2个台阶,或者每次走3个台阶,或者每次走5个台阶,请问这n个楼梯总共有几种走法。

n个阶梯的问题,可以分解成三个小问题,即n-2个阶梯有几种走法,n-3个阶梯有几种走法,n-5个阶梯有几种走法,而n个阶梯的走法就是这三种走法的和。或者可以反过来思考,你已经站在最后一个台阶上了,那么到达最后一个台阶的情况只能是三种情况,最后一步恰好走2个台阶恰好到达,最后一步恰好走3个台阶恰好到达,最后一步恰好走5个台阶恰好到达。通过这个思想,我们就可以写出递归形式的代码。

publicstaticintrecursion(intn){if(n<0){return0;}if(n==0){return1;}returnrecursion(n-5)+recursion(n-3)+recursion(n-2);}显然上面递归的处理方式需要重复计算很多子问题,画出递归调用的图就一目了然,由于该图和上一个问题的图很类似,这里就省略了。因此就创建一张表,把子问题的结果都记录下来,dp[i]表示走到第i个阶梯有多少种走法。按照套路,我们应该从小的阶梯数开始填表。

publicstaticintdynamic(intn){int[]record=newint[n+1];record[0]=1;for(inti=0;i=0record[i-2]:0;intn3=i-3>=0record[i-3]:0;intn5=i-5>=0record[i-5]:0;record[i]=n2+n3+n5;}returnrecord[n];}同样,这里例子中也不存在最优问题。

问题描述:从顶部出发在每一个节点可以选择向下或者向右下走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大。

对于上图所示的数塔:最大值为379,绿色的的数字就是被选择的节点。

这个问题不能使用贪心算法,请大家自己用三层的阶梯列举出反例。我们现在试着将这个问题分解成子问题,如下图所示。想求得最大值,我们只要选择的红色边框数塔最大值和蓝色边框数塔的最大值中更大的那个,然后加上32,就整个数塔的最大值。这样我们就将一个大的问题转化成了两个规小的问题,然后这两个规模较小的问题还可以继续分解成更小的子问题。根据这个思路,我们可以得到如下递归形式的代码。

上图中的紫色边框数塔既存在于红色边框数塔中,也存在于蓝色边框数塔中,会重复计算两次。实际上,我们使用递归时重复计算的问题显然不止这一个,所以效率不高。为此我们应该创建一张和数塔形状一样的三角形表用来记录更小的数塔的最大值。我们table表示这个表,表中table[i][j]位置的值表示以(i,j)为顶点的数塔的最大值。我们用a[i][j]表示数塔中第i行,第j列的值。那么table[i][j]=a[i][j]+Math.max(table[i-1][j],table[i-1][j-1])。按照套路,我们应该从最小的数塔开始填表。按照table[i][j]的定义,table表的最下面一行就应该等于数塔表中的最下面一行。

按照定义,我们就可以填倒数第二行的dp[i][j]。

table[4][0]=79+Math.max(0,71)=150table[4][1]=69+Math.max(71,51)=140table[4][2]=78+Math.max(51,82)=160table[4][3]=29+Math.max(82,91)=120table[4][4]=63+Math.max(91,64)=154填入到table表的倒数第二行,如下图所示

有了倒数第二行,我们就可以推出倒数第三行,依次类推,我们就可以得到最上面table[0][0]的数值,它就表示了整个数塔的最大值。除了最大值,如果我们还需要知道走了哪些路径,我们还应该定义一个path表,在填table[i][j]时,同时填写path[i][j]。path[i][j]表示了以(i,j)为顶点的数塔的最大值是由两个子数塔(table[i-1][j]为顶点的数塔和table[i-1][j+1]为顶点的数塔)中的哪一个得到的。

最大值:379路径为:行:0,列:0行:1,列:0行:2,列:1行:3,列:2行:4,列:2行:5,列:33.4零-壹背包问题问题描述:有n个物品,它们有各自的重量(weight)和价值(value),现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和此时背包中的物品?一个物品只有不拿和拿两种情况,可以用0和1表示,所以称之为0-1背包问题。

我们来看一个具体的例子。假设有如下物品:

求背包容量在10的时候的能装物品的最大价值,以及装了哪些物品

我们可能首先想到的是贪心算法,我们算出每种物品的单位重量价值(weight/value),然后按照单位重量价值排序。我们放入物品时首先选择单位重量价值高的物品,直到放不下为止。但是很遗憾,这样得不到最优解。我们不妨列举一个极端的例子,假设只有两个物品,A的value=2.9,weight=2.1;B的value=3,weight=3,显然物品A的单位重量价值要大于B的单位重量价值,但对于容量为3的背包,我们应该选择物品B,所以贪心算法失效。对于0-1背包问题,贪心选择之所以不能得到最优解是因为:它无法保证最终能将背包装满,而部分闲置的背包空间使每公斤背包空间的价值降低了。

回到上面具体的这个问题,它可以表述为

maxValue{宝石、剃须刀、ipad、充电宝、iphone|背包容量10},

每个物品只有选和不选两种结果,我们不妨从第一个物品开始。如果选了宝石,那么问题转化为当前背包已有价值为50,并在剩下的背包容量(10-4)的前提下,再剩下的物品中(即剃须刀、ipad、充电宝、iphone)选取出最大的价值;如果不选宝石,那么问题转化为当前背包价值为0,并在剩下的背包容量10的前提下,在剩下的物品中(即剃须刀、ipad、充电宝、iphone)选取出最大的价值。我们只需要选择:

50+maxValue{剃须刀、ipad、充电宝、iphone|背包容量6}

0+maxValue{剃须刀、ipad、充电宝、iphone|背包容量10}

中较大的那个。而这就直接转化成两个子问题的求解,显然我们已经可以用分治的方式解决这个问题了。我们不妨把递归树(或者说分治树)画出来。

上图就是0-1背包问题的递归树,图左文字边表示当前可选的物品,节点中的值表示背包的容量。我们没有把整个递归树全部都画出来,因为图中我们就已经发现了需要重复计算的子问题。如果背包容量变大,物品种类变多,那么需要重复计算的子问题就越多。需要说明的是上图中有三个背包容量为7的子问题,但是只有被标记的两个子问题才是重复的子问题,因为这两个子问题的背包容量一样,可选物品一样。为了避免子问题的重复求解,我们就建立一张动态规划表,下次遇到重复的子问题,我们就直接查表。下图表示了动态规划表和递归树之间的关系。

那我们现在的主要问题就变成了如何填这样一张表。我们用一个名为dp的二维数组表示这张表,dp[0]行需要单独初始化,从dp[1]行开始填表,规则:从左到右,从上到下。

dp[i][j]表示前i个物品(包括物品i),在背包容量为j时能装的最大价值。

dp[i][j]为下面两者的最大值:

1)物品i不放入背包中:背包容量为j时,前i-1个物品组合出的最大价值

2)物品i放入背包中:物品i的价值+除去物品i占用的重量后,剩余背包容量j-weight(i)由前i-1个物品组合出的最大价值

用公式表示为

1)如果dp[i][j]>dp[i-1][j],说明该物品i被放入到了背包中,令i=i–1,j=j–weight[i],然后重复步骤1。

2)如果dp[i][j]==dp[i-1][j],且只想得到背包最大价值的其中一种的物品一种组合方式,不妨认为该物品i没有被放入到了背包中,令i=i–1,重复步骤1)。

对于步骤2),如果

dp[i][j]==dp[i-1][j]&&dp[i][j–weight(i)]+value(i)==dp[i][j]

说明物品i可以放入背包中(令i=i–1,j=j–weight[i]),也可以不用放入背包中(令i=i-1)。这里就产生分支,说明放入背包中的物品组合方式不唯一,为了简单起见,我们找到一种物品的组合方式即可。

160[4,3,2,0]如果我们仅仅需要知道最大的价值,不需要知道装了哪些物品,我们就可以对空间复杂度进行优化,动态规划表只需要一维,因为dp[i][]仅和dp[i-1][]有关。

Givenanon-emptyarraycontainingonlypositiveintegers,findifthearraycanbepartitionedintotwosubsetssuchthatthesumofelementsinbothsubsetsisequal.

Note:

1.Eachofthearrayelementwillnotexceed100.

2.Thearraysizewillnotexceed200.

Example1:

Input:[1,5,11,5]

Output:true

Explanation:Thearraycanbepartitionedas[1,5,5]and[11].

Example2:

Input:[1,2,3,5]

Output:false

Explanation:Thearraycannotbepartitionedintoequalsumsubsets.

这是LeetCode的原题。这个问题本质上还是0-1背包问题,背包容量是数组之和的一半,物品的价值和体积是1比1的关系,额外条件是需要把背包装满。

问题描述:有n种物品,它们有各自的重量(weight)和价值(value),现有给定容量的背包,每种物品可以拿任意多个,如何让背包里装入的物品具有最大的价值,以及每种物品装了几个?

假设,我们还是利用0-1背包中的物品,背包容量为11。

完全背包问题也可以转化成0-1背包问题。因为第i个物品最多拿“背包重量/(物品i的重量)”个,也就是说在0-1背包问题中每个物品i占一行,完全背包问题中,每个物品占“背包重量/(物品i的重量)”个行,按照这个思路显然已经能够解决这个问题。现在我们不把这个问题转化为0-1背包问题,而从这个问题的根源直接思考。

完全背包问题可以表述为

maxValue{宝石、剃须刀、ipad、充电宝、iphone|背包容量10}

每个物品只有选和不选两种结果,我们不妨从第一个物品开始。如果选了宝石,那么问题转化为当前背包已有价值为50,并在剩下的背包容量(10-4)的前提下,继续在{宝石、剃须刀、ipad、充电宝、iphone}选取出最大的价值;如果不选宝石,那么我们就在{剃须刀、ipad、充电宝、iphone}中选择一种,那么问题转化为当前背包价值为0,并在剩下的背包容量10的前提下,再剩下的物品中即{剃须刀、ipad、充电宝、iphone}选取出最大的价值。

因此我们只需要选择:

50+maxValue{宝石、剃须刀、ipad、充电宝、iphone|背包容量6}

中较大的那个。

而这就直接转化成两个子问题的求解,显然我们已经可以用分治的方式解决这个问题了。我们同样可以把递归树画出来,同样还会发现存在需要重复求解的子问题,为了避免子问题的重复求解,我们还是建立一张动态规划表,下次遇到重复的子问题,我们就直接查表。这里我们直接给出动态规划表,我们用一个名为dp的二维数组表示这张表,dp[0]行单独初始化,从dp[1]行开始填表,规则:从左到右,从上到下。

dp[i][j]为下面二者的最大值:

同样,从dp表中我们还可以知道哪些物品被选择了,选择多少次。我们还是从右下角开始回溯。

1)dp[i][j]>dp[i-1][j]说明i号物品被选择了,j=j–weight[i]

2)dp[i][j]==dp[i-1][j]为了简单起见,我们认为i号物品没有被选择,令i=i-1(实际上这里同样可能存在分支,即最大价值时物品的组合方式和数量并不唯一,我们这里为了简单处理,就不考虑这个问题了)。

Youaregivencoinsofdifferentdenominations([dnɑ:mnen]面额)andatotalamountofmoneyamount.Writeafunctiontocomputethefewestnumberofcoinsthatyouneedtomakeupthatamount.Ifthatamountofmoneycannotbemadeupbyanycombinationofthecoins,return-1。

Input:coins=[1,2,5],amount=11

Output:3

Explanation:11=5+5+1

Input:coins=[2],amount=3

Output:-1

我们首先看一下子序列的定义。假设给定一个字符串,我们抽取任意多个不超过字符串长度的字符,并保持它们的前后关系,这样的字符我们称之为子序列。对于字符串ABCDEFG而言,BEF、C、AG等等都是它的一个子序列。

Longestcommonsequence问题:给定两个字符串s1和s2,求这两个字符串中最长的公共子序列。比如给定两个字符串s1:bdcaba和s2:abcbdab,它们的公共子序列

长度为4,最长公共子序列是:bcba。

字符串s1的长度用n表示,字符产s2的长度用m表示,字符串s1和s2的最长公共字串用lcs(n,m)。那么这个问题可以转化为三个子问题

1)求lcs(n-1,m-1)

2)求lcs(n-1,m)

3)求lcs(n,m-1)

当我们求的上述三个子问题的答案,那么lcs(n,m)的结果就可以通过如下方式得到:

如果s1[n]==s2[m]

lcs(n,m)=lcs(n-1,m-1)+1

如果s1[n]!=s2[m]:

lcs(n,m)=max{lcs(n-1,m-1),lcs(n-1,m),lcs(n,m-1)}

但是实际上lcs(n,m)只要转化成两个子问题lcs(n-1,m)和lcs(n,m-1)就好了。

而子问题lcs(n-1,m-1)是没有必要的,因为lcs(n-1,m-1)必定小于等于lcs(n-1,m)和lcs(n,m-1)中的en任意一个。从常理上来说很好理解,不可能两个字符串中的任意一个变长了,公共子序列反而减少了。而本质上是由于lcs(n-1,m-1)也是lcs(n-1,m)和lcs(n,m-1)这两个问题的子问题。

通过上面的分析,我们把大的问题转化成小的问题,就可以通过递归(或者说分治)的方式把问题解决了,下面就是递归对应的代码。

第0行,表示“b”和“bdcaba”的公共子序列,可以单独处理,同理第0列也可以单独处理,填表完成后如上图所示。从第二行开始,dp表按照从上到下,从左到右的填表顺序填表。根据子递归中子问题的定义,dp[i][j]的取值如下:

当填完整张表时,右下角的值就是公共子序列的最大长度。如果我们还需要知道公共子序列是什么,那么我们可以从右下角开始回溯,如果dp[i][j]>dp[i-1][j]且dp[i][j]>dp[i][j-1],说明s1[i]或者s2[j]是公共子序列,否则选择走dp[i-1][j]和dp[i][j-1]中较大的那个,同样第0行要单独处理。

贪心算法:如果为了方便的解决这个问题,我们需要将大问题化简成小问题,在所有小问题中,仅选择对当前最有利的小问题作为我们解决大问题的基础。

动态规划:如果为了方便的解决这个问题,我们需要将大问题化简成小问题,记录已解决过的小问题,将所有小问题中的最优解作为我们解决大问题的基础。换句话说,能用贪心算法解决的,动态规划算法也肯定能解决,反之不成立。

能用动规解决的问题的特点

1)问题具有最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。

2)无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。

THE END
1.请解释动态规划算法的原理,并通过一个具体例子进行说明。而使用动态规划,则可以避免重复计算。 具体实现方法是使用一个数组来存储已经计算过的子问题的解,每次计算一个子问题时,先查看该子问题是否已经计算并记录在数组中,如果已经计算过,则直接使用已有的结果,否则进行计算并将结果存入数组中。 通过动态规划算法,我们可以在一次遍历中计算出整个斐波那契数列,大大提高了计算https://easylearn.baidu.com/edu-page/tiangong/questiondetail?id=1792780326003484744&fr=search
2.动态规划实验原理实验报告总结.pptx结果与预期的差异分析实验总结与展望CATALOGUE05通过本次实验,我们深入了解了动态规划的基本原理和应用场景,掌握了如何将问题分解为子问题并解决子问题以解决原问题的策略。深入理解动态规划原理在实验过程中,我们通过编写代码实现了动态规划算法,提高了编程能力和解决问题的能力。提高了编程能力通过解决实际问题,我们培养了https://www.renrendoc.com/paper/306178172.html
3.五大常用算法之二:动态规划算法151CTO博客递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。 https://blog.51cto.com/u_12667998/6544848
4.算法之动态规划(DynamicProgramming)动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关https://www.jianshu.com/p/c94134d39df2
5.彻底搞懂KMP算法原理腾讯云开发者社区彻底搞懂KMP算法原理 简介 KMP算法是什么? 引用自百度百科: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过https://cloud.tencent.com/developer/article/2235837
6.算法动态规划DynamicProgramming从菜鸟到老鸟由上面的图片和小故事可以知道动态规划算法的核心就是记住已经解决过的子问题的解。 动态规划算法的两种形式 上面已经知道动态规划算法的核心是记住已经求过的解,记住求解的方式有两种:①自顶向下的备忘录法②自底向上。 为了说明动态规划的这两种方法,举一个最简单的例子:求斐波拉契数列**Fibonacci **。先看一下这https://blog.csdn.net/u013309870/article/details/75193592
7.Java使用动态规划算法思想解决背包问题java动态规划算法的最优性原理:一个最优决策序列具有这样的性质,不论初始状态和第一步决策如何,对前面的决策所形成的状态而言,其余的决策必须按照前一次决策所产生的新状态构成一个最优决策序列。最优性原理体现为问题的最优子结构特性,对于一个问题,如果能从较小规模的子问题的最优解求得较大规模同类子问题的最优https://www.jb51.net/article/246495.htm
8.科学网—经典的算法回顾1.最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。 2.子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重https://blog.sciencenet.cn/blog-315535-665392.html
9.阿里巴巴算法岗武功秘籍(下)● 棋子在规定走法,规定大小的棋盘上,N步后还在棋盘上的概率,主要考察动态规划 ● 一个NxN的棋盘,一个棋子可以等概率地跳八个方向(和象棋中马一样的跳法)。当这个棋子跳出棋盘范围的时候,就停止。问棋子跳了k步之后,棋子还留在棋盘的概率。 ● 学校男生的概率2/3,女生的概率1/3,男生穿牛仔的概率2/3,https://www.flyai.com/article/928
10.基于V2G的电动汽车有序充放电控制策略参考文献[6]以电动汽车的充电设施为研究控制对象,采用多目标以及分层分区的有序充电优化控制模型。在此基础上,采用序列二次规划算法和动态规划算法进行求解,通过IEEE34节点算例分析验证了模型和算法的有效性。 文中分别从电网侧和用户侧的角度出发,基于V2G构建了不同的电动汽车有序充放电模型,采用粒子群优化算法进行http://qks.cqu.edu.cn/html/cqdxzrcn/2019/1/20190101.htm
11.优化面向智能交通的整数规划问题运筹OR帷幄时空网络建模方法通常采用动态规划方法进行高效求解,动态规划可基于Bellman格式实现[21, 22]。时空网络下的动态规划算法广泛用于自动车路径规划[23, 24],公路传感器选址[25]等问题。 5.二次分配问题的扩展应用 交通运输领域很多问题可归结为指派问题,其中的经典的指派问题又分为线指派问题和二次分配问题两大类。Tjallihttps://www.shangyexinzhi.com/article/5213211.html
12.全局路径规划本系列就从无人驾驶路径规划的这两方面进行展开,对一些经典的算法原理进行介绍,并根据个人的一些理解和想法提出了一些改进的意见,通过Matlab2019对算法进行了仿真和验证。过程中如果有错误的地方,欢迎在评论区留言讨论,如有侵权请及时联系。 那么废话不多说,直接进入第一https://mp.weixin.qq.com/s?__biz=MzU1NjEwMTY0Mw==&mid=2247570745&idx=1&sn=9723558902c987664d082aa332663b82&chksm=fbc9ac5dccbe254b66b4d31e7f0629671d1945835c7f06f3464fb7796532c4ae91e703b36f4f&scene=27