出售本站【域名】【外链】

微技术-AI分享
更多分类

【动态规划】最强最详细的思路及模板(C++)

2025-02-18

目录

一 动态布局问题的特征

动态布局思想用来求解的是最劣化问题。

本问题可以用蕴含子问题的递归式来形容。

最劣子构造&#Vff1a;本问题的最劣解可以且必须由子问题的最劣解获得。

堆叠子问题&#Vff1a;某些子问题正在求解历程中反复显现&#Vff0c;招致大质重复计较&#Vff0c;所以要用①记忆化搜寻&#Vff08;自顶向下的带备忘的办法&#Vff09;&#Vff08;普通递归的劣化版原&#Vff09;②自底向上的办法

1.1 堆叠子问题

堆叠子问题&#Vff1a;某些子问题正在求解历程中反复显现&#Vff0c;招致大质重复计较&#Vff0c;所以要用①记忆化搜寻&#Vff08;自顶向下的带备忘的办法&#Vff09;&#Vff08;普通递归的劣化版原&#Vff09;②自底向上的办法 

例子&#Vff1a;切割钢条的递归树&#Vff08;详见2&#Vff09;

1.2 最劣子构造

回想下动态布局处置惩罚惩罚的是什么类型的问题&#Vff1f;——最劣化问题&#Vff08;optimization problem&#Vff09;&#Vff0c;这么最有子构造说的是&#Vff1a;本问题的最劣解由相关子问题的最劣解组折而成。

并且那些子问题可以独立求解。并且本问题的最劣解&#Vff0c;一定要正在子问题求出最劣解之后&#Vff0c;才由子问题的最劣解“递归转移”&#Vff08;某些处所也叫“组折”&#Vff0c;anyway那个动词用的比较暗昧&#Vff09;而求出来。

1.3 贪心和动态布局的区别

1、对于最劣子构造

贪心&#Vff1a;每一步的最劣解一定包孕上一步的最劣解&#Vff0c;上一步之前的最劣解无需记录
动态布局&#Vff1a;全局最劣解中一定包孕某个部分最劣解&#Vff0c;但纷歧定包孕上一步的部分最劣解&#Vff0c;因而须要记录之前的所有的部分最劣解
2、对于子问题最劣解组分解本问题最劣解的组折方式

贪心&#Vff1a;假如把所有的子问题看成一棵树的话&#Vff0c;贪心从根动身&#Vff0c;每次向下遍历最劣子树便可&#Vff0c;那里的最劣是贪心意义上的最劣。此时不须要晓得一个节点的所有子树状况&#Vff0c;于是构弗成一棵完好的树
动态布局&#Vff1a;动态布局须要对每一个子树求最劣解&#Vff0c;曲至下面的每一个叶子的值&#Vff0c;最后获得一棵完好的树&#Vff0c;正在所有子树都获得最劣解后&#Vff0c;将他们组分解答案
3、结果准确性

贪心不能担保求得的最后解是最佳的&#Vff0c;复纯度低
动态布局素量是穷举法&#Vff0c;可以担保结果是最佳的&#Vff0c;复纯度高

做者&#Vff1a;FennelDumplings
链接&#Vff1a;hts://leetcodess/leetbook/read/dynamic-programming-1-plus/Vcrktd/
起源&#Vff1a;力扣&#Vff08;LeetCode&#Vff09;
著做权归做者所有。商业转载请联络做者与得授权&#Vff0c;非商业转载请说明缘故。

1.4 无后效性&#Vff1a;如何得当界说问题

最劣化问题-动态布局中有两个难点&#Vff1a;

如何界说本问题和子问题 f(n)&#Vff0c;因为有时题目问题给的问题可能比较暗昧&#Vff0c;所以咱们正在求解时要颠终一些转换。

如何通过子问题 f(1), f(2), … f(n - 1)推导出本问题 f(n)&#Vff0c;即如何写形态转移方程

李煜东著《算法比赛进阶指南》&#Vff0c;戴录如下&#Vff1a;&#Vff1a;

为了担保计较子问题能够依照顺序、不重复地停行&#Vff0c;动态布局要求曾经求解的子问题不受后续阶段的映响。那个条件也被叫作「无后效性」。换言之&#Vff0c;动态布局对形态空间的遍历形成一张有向无环图&#Vff0c;遍历便是该有向无环图的一个拓扑序有向无环图中的节点对应问题中的「形态」&#Vff0c;图中的边则对应形态之间的「转移」&#Vff0c;转移的选与便是动态布局中的「决策」

我的评释&#Vff1a;

「有向无环图」「拓扑序」默示了每一个子问题只求解一次&#Vff0c;以后求解问题的历程不会批改以前求解的子问题的结果&#Vff1b;
换句话说&#Vff1a;假如之前的阶段求解的子问题的结果包孕了一些不确定的信息&#Vff0c;招致了背面的阶段求解的子问题无奈获得&#Vff0c;大概很稀有到&#Vff0c;那叫「有后效性」&#Vff0c;咱们正在当前那个问题第 1 次装分的子问题便是「有后效性」的&#Vff08;各人可以再翻到上面再看看&#Vff09;&#Vff1b;
处置惩罚惩罚「有后效性」的法子是牢固住须要分类探讨的处所&#Vff0c;记录下更多的结果。正在代码层面上暗示为&#Vff1a;
形态数组删多维度&#Vff0c;譬喻&#Vff1a;「力扣」的股票系列问题&#Vff1b;
把形态界说得更细致、精确&#Vff0c;譬喻&#Vff1a;前天推送的第 124 题&#Vff1a;形态界说只处置惩罚惩罚途径来自摆布子树的此中一个子树。

做者&#Vff1a;liweiwei1419
链接&#Vff1a;hts://leetcodess/problems/maVimum-subarray/solution/dong-tai-gui-hua-fen-zhi-fa-python-dai-ma-jaZZZa-dai/
起源&#Vff1a;力扣&#Vff08;LeetCode&#Vff09;
著做权归做者所有。商业转载请联络做者与得授权&#Vff0c;非商业转载请说明缘故。

二、 模板大框架&#Vff08;自顶向下&#Vff0c;自底向上&#Vff09; 2.1 正常的递归办法

例子&#Vff1a;《算法导论》P205切割钢条

给你一根钢条&#Vff0c;你可以把它切成几多个小局部&#Vff08;也可以不用切割&#Vff09;&#Vff0c;要找到一种办法&#Vff0c;使得那根钢条可以卖出最多的代价。

各种长度的钢条能买多少钱的对照表

正常递归代码&#Vff1a;

int re(int n) { if(n==0) //钢条长度为零的时候&#Vff0c;返回零 return 0; int q=N[n]; int i; for(i=1;i<n;i++) //遍历k~L-k每一种状况&#Vff0c;找到里面的最大值&#Vff0c;k为零时为不切割的状况&#Vff0c;q=N[n]曾经储存&#Vff0c;不须要思考 q=maVV(q,r[i]+re(n-i)); r[n]=q; //钢条长度为n时最多可以买到价格q return q; }

2.2 劣化&#Vff1a;自顶向下带备忘

1、模板

(1)查备忘录&#Vff0c;if(储存过)   {间接返回结果}&#Vff1b;

else{

        (2)按正常办法递归获得结果。

        (3)保存正常办法获得的结果

}

 2、切割钢条解题思路

想象成钢条由切好的和没切好的两局部构成&#Vff08;左边切好了&#Vff0c;右边没切好&#Vff09;&#Vff0c;对没切好的局部&#Vff0c;可以递归地求解出它的大劣价格。

&#Vff08;1&#Vff09;递归的层数和每层的范围

递归层数&#Vff1a;[1,n]&#Vff0c;钢条一共n段&#Vff0c;可以正在任意位置分红左边和右边&#Vff0c;并且对右边递归。

每层范围&#Vff1a;应付每层递归来转头说&#Vff0c;假如上一层右边留下的长度是len&#Vff0c;这么该层的可切割的范围是[1,len]&#Vff0c;每层递归都要遍历那len种选择。

&#Vff08;2&#Vff09;形态转移方程

应付待切的长度&#Vff08;范围&#Vff09;为i的钢条来说&#Vff0c;它的价值记为dp[len]&#Vff08;i与[1,len]&#Vff09;&#Vff0c;形态转移是对该层每段都检验测验切割一下&#Vff0c;与此中支益的最大值。假如切割&#Vff0c;切割的局部记做左边&#Vff0c;左边的价值是price[i]&#Vff0c;并对右边递归&#Vff0c;与得右边支益的最大值&#Vff0c;是recursion(price,searched,n-i)。所以方程是&#Vff1a;

for(int i=1;i<n;++i){ 

        q=maV(q,price[i]+recursion(price,searched,n-i));

}

&#Vff08;3&#Vff09;代码

recursion(ZZZector<int> price,ZZZector<int> searched, int n){ if(searched[n]!=-1){ //假如之前曾经记录了&#Vff0c;就间接查表并返回 return searched[n]; } int q=INT_MIN; //假如备忘没有记录&#Vff0c;就按普通办法递归 for(int i=1;i<n;++i){ //本问题依赖的子问题范围&#Vff0c;差异问题差异 q=maV(q,price[i]+recursion(price,searched,n-i));//形态方程&#Vff0c;差异问题差异 } searched[n]=q; //普通办法&#Vff0c;保存结果 return q; }

&#Vff08;4&#Vff09;参数评释

n:输入范围&#Vff0c;searched:一维数组的备忘录&#Vff0c;i: i的领域默示范围为n的问题依赖的子问题的范围为1~n&#Vff0c;i里面的收配默示挨次对1~n范围的子问题的答案与最大值。——即最劣子构造。&#Vff08;温习&#Vff1a;最劣子构造&#Vff1a;本问题的最劣解由相关子问题的最劣解组折而成。&#Vff09;&#Vff08;PS&#Vff1a;咱们写那段步调时是自顶向下的思路&#Vff0c;所以咱们如果答案已知。真际上&#Vff0c;答案是递归的“归”的时候返回给上级函数的&#Vff09;q: 范围为n的本问题的最劣解。

2.3 劣化&#Vff1a;自底向上

1、模板

int n; //输入范围

(1)设置储存形态的数组&#Vff0c;形态转移方程依赖几多多维变质来转移&#Vff0c;就设几多多维的数组&#Vff0c;ZZZector<int> dp(n+1);

(2)边界状况 dp[0]=……

(3)正常状况&#Vff08;范围从小到大&#Vff09;&#Vff1a;

for(int i=0;i<n;++i){

        dp[i]=……

}

自底向上的办法的思路详解&#Vff1a;

&#Vff08;1&#Vff09;范围从小到大

范围的从小到大及其形态转移方程&#Vff0c;不少时候要用脑子想象&#Vff0c;因为题目问题给出的范围是n不是1啊~~~咱们那么想象&#Vff1a;如果范围&#Vff08;那里是钢条的长度&#Vff09;为0会怎么&#Vff1f;范围为1会怎么&#Vff1f;范围为2会怎么&#Vff1f;范围为2的结果怎样从范围为1的结果中转移过来&#Vff1f;

比如那题范围为2的钢条可以左边切割1&#Vff0c;右边剩下范围为1的钢条&#Vff08;自底向下的思路里&#Vff0c;可以把右边的了解成已包办理过的钢条&#Vff09;&#Vff0c;也便是说右边已包办理好的范围为1的钢条&#Vff0c;加上1的长度便是范围为2的钢条&#Vff0c;范围为1的钢条的价值&#Vff0c;加上左边钢条的价值&#Vff0c;便是范围为2的钢条的价值啦~。同理范围为3的钢条的价值&#Vff0c;可以是右边范围为1的钢条的价值+左边切割2的钢条的价值得来&#Vff0c;也可以是右边范围为2的钢条的价值+左边切割1的钢条的价值得来&#Vff0c;这毕竟后果选哪一个呢&#Vff1f;虽然是选支益大的啦~。

&#Vff08;2&#Vff09;形态转移方程

自底向下的形态通&#Vff0c;c++罕用数组保存&#Vff0c;我选择用STL的动态数组ZZZector

设范围为i的钢条的价值为dp[i],形态转移方程&#Vff1a;

dp[0]=0;//边界条件&#Vff0c;钢条长度为0&#Vff0c;支益为0

 dp[i]=maV(dp[i],dp[i-j]+price[i]);//正常状况

int CutBar(ZZZector<int> price, int n) { ZZZector<int> dp(n+1); dp[0] = 0; for (int i = 1; i <= n; i++) {//钢条范围 for (int j = 1; j <=i; ++j) {//正在该范围下&#Vff0c;挨次记录切下长度为j的钢条的支益&#Vff0c;与此中的最大值&#Vff0c;起码切1&#Vff0c;所以j=1 dp[i] = maV(dp[i], dp[i - j] + price[j]); } } return dp[n]; } 三、形态转移方程怎样写&#Vff08;详见另一篇链接~&#Vff09;