动态规划的介绍

动态规划就是一种用来解决最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解的子问题的解都记录下来。这样当下次碰到同样的子问题的时候 ,直接使用之前记录的结果。而不是重复计算。

     动态递归的递归写法

先来讲解递归写法。通过这部分内容的学习,就是为了让我理解如何记录子问题的解,来避免下次遇到相同的子问题时的重复计算。以斐波那契数列为列,斐波那契数列的定义F0=1,F1=1,Fn=F(n-1)+F(n-2) (n>=2)

int F(int n){

if(n==0||n==1)return 1;

else return F(n-1)+F(n-2);

}

事实上,这个递归会 涉及很多的重复的计算,如图当n==5,可以得到F(5)=F(4)+F(3),接下来计算F(4)=F(3)+F(2).

这时候如果不采取措施的话,F(3)将会被计算两次。可以推知,如果n很大的话,重复计算的次数将难以计算,事实上,由于没有及时保存中间计算的结果,实际复杂程度会高达O(2^N),即每次都会计算F(n-1)和F(n-2)这两个分支。基本不能承受n较大的情况为了避免重复计算,可以开一个一维数组dp,用以保存已经计算过的结果。其中dp[n]来记录F[n]的结果,并用dp[n]=-1表示F[n]当前还没有被计算过。int dp[MAXN].                                                                                                                                                     然后就可以在递归当中判断dp[n]是否是-1,如果不是-1,说明已经计算过F(n),直接返回dp[n]就是结果。否则,就按照递归式进行递归,代码如下。

int F(n){

if(n==0||n==1)return 1;//递归边界

if(dp[n]!=-1)return dp[n];//已经计算过,直接返回结果,不在重复计算。

else{

dp[n]=F[n-1]+F[n-2]

return dp[n];//返回F[n]的结果。

}

}    

这样就把已经计算过的内容已经记录下来了,于是当下次再碰到需要计算相同内容时候,就能直接使用上次计算的结果,这可以省去大半无效无效计算。这也是记忆话搜索这个名字的由来。

动态规划的递推写法。

以经典的数塔为列,将一些数字排成塔形状,其中第一层有一个数字,第二层有二个数字。。。。。。。第n层有n个数字。现在要从第一层走到第n层,每次只能走向下一层连接的两个数字中的一个,问:最后将路径上所有数字相加后得到和最大是多少?

这里大家如何不知道什么事数塔问题的话,大家自行百度,我真的不会作图,,我的论文作文,我还着急呢。慌的一批。  

由于上面的考虑,不妨令dp[i][j]表示第i行第j个数字出发的到达最底层所有路径中能得到的最大和,列如dp[3][2]就是图中的7到底层的最大和,在定义这个数组之后,dp[1][1]就是最终想要的答案,现在我们是想办法求出他,

注意到一个细节:如果要求出“从位置(1,1)到达最底层的最大和”dp[1][1],那么一定要先求出它的两个子问题“从位置(2,1)到达最底层的最大和dp[2][1]”和从位置dp[2][2]到达最底层的最大和dp[2][2],进行了一次决策。写成式子的话就是

dp[1][1]=max(dp[2][2],dp[2][1])+f[1][1] 

由此可以总结出来一个规律

dp[i][j]=max(dp[i+1][j+1],dp[i+1][j])+f[i][j]   

吧dp[i][j]称为问题的状态,而把上面的式子称为状态转移方程,他吧状态dp[i][j]转移为dp[i+1][j]和dp[i+1][j+1].可以发现,状态dp[i][j]只与i+1层的状态有关。而与其它层的状态有关。这样的层号为i的状态就总是可以由i+1层的两个子状态有关。而与其它层无关。那么如果将层号增大,什么时候会到头呢?可以发现,数塔的最后一层的dp值总是等于元素本身。即dp[n][j]=f[n][j](1<=j<=n)吧这种可以直接确定其结果的部分称为边界,而动态规划的递推写法总是从这些边界出发,通过状态转移方程扩散的整个dp数组。

int f[maxn][maxn],dp[maxn][maxn]

            int main(){

           int n;

scanf("%d",&n);

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

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

scanf(“%d”,&f[i][j]);

}

//边界

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

dp[n][j]=f[n][j];

}

//从第n-1层不断往上计算出 dp[i][j]

for(int i=n-1;i>=1;i--){

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

dp[i][j]=max(dp[i+1][j]+dp[i+1][j+1])+f[i][j];

}

}

}

}

显然,使用递归也可以实现上面的列子。两者的区别是在于:使用递推写法的计算方式是自底向上,即从边界开始,不断向上解决问题,直到解决了目标问题,而使用递归写法的计算方式是自顶向下,即从目标问题开始,将从目标问题开始。将他分解子问题的组合。直到分解至边界为止。

                   

      

猜你喜欢

转载自blog.csdn.net/ZSS1753936255/article/details/81747757