zoukankan      html  css  js  c++  java
  • 动态规划——递归写法和递推写法

    一、什么是动态规划

      动态规划(DP)是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。

     

    二、动态规划的递归写法

      以斐波那契(Fibonacci) 数列为例,斐波那契数列的定义为 F0=1,F1=1,Fn=Fn-1+Fn-2 (n≥2)。为了避免重复计算,可以开一个一维数组 dp,用以保存已经计算过的结果。代码如下:

     1 int dp[maxn];
     2 // 斐波那契数列递归写法
     3 int F(int n) {
     4     if(n == 0 || n==1)    return 1;    // 递归边界
     5     if(dp[n] != -1)    return dp[n];    // 已经计算过 
     6     else {
     7         dp[n] = F(n-1) + F(n-2);    // 计算F(n),并保存 
     8         return dp[n];                // 返回 F(n) 结果 
     9     } 
    10 } 

    三、 动态规划的递归写法

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

      

      不妨令 dp[i][j] 表示从第 i 行第 j 个数字出发到达最底层的所有路径中能得到的最大和,在定义了这个数组后,dp[1][1] 就是最终想要的答案。

      如果想求出 dp[i][j],那么一定要先求出它的两个子问题“从位置 (i+1,j) 到达最底层的最大和 dp[i+1][j]”和“从位置 (i+1,j+1) 到达最底层的最大和 dp[i+1][j+1]”,即进行了一次决策:走左下还是右下。写成式子就是:

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

      其中 f[i][j] 存放第 i 层的第 j 个数字。代码如下:

     1 /*
     2     动态规划的递推写法 
     3 */
     4 
     5 #include <stdio.h>
     6 #include <string.h>
     7 #include <math.h>
     8 #include <stdlib.h>
     9 #include <time.h>
    10 #include <stdbool.h>
    11 
    12 #define maxn 1000
    13 int f[maxn][maxn], dp[maxn][maxn]; 
    14 
    15 // 较大值 
    16 int max(int a, int b) {
    17     return a>b ? a : b;
    18 }
    19 
    20 int main() {
    21     int n;
    22     scanf("%d", &n);
    23     int i, j;
    24     for(i=1; i<=n; ++i) {
    25         for(j=1; j<=i; ++j) {
    26             scanf("%d", &f[i][j]);        // 输入数塔 
    27         }
    28     }
    29     // 边界
    30     for(j=1; j<=n; ++j) {
    31         dp[n][j] = f[n][j];
    32     } 
    33     // 从第 n-1 层往上计算 dp[i][j]
    34     for(i=n-1; i>=1; --i) {
    35         for(j=1; j<=i; ++j) {
    36             dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + f[i][j]; 
    37         }
    38     } 
    39     printf("%d
    ", dp[1][1]);            // dp[1][1] 即为需要的答案 
    40 
    41     return 0;
    42 }

       

      下面指出两对概念的区别:

      1.  分治与动态规划。分治和动态规划都是将问题分解为子问题,然后合并子问题的解得到原问题的解。但是不同的是,分治法分解出的子问题是不重叠的,因此分治法解决的问题不拥有重叠子问题,而动态规划解决的问题拥有重叠子问题。另外,分治法解决的问题不一定是最优化问题,而动态规划解决的问题一定是最优化问题。

      2.  贪心与动态规划。贪心和动态规划都要求原问题必须拥有最优子结构。二者的区别在于,贪心法通过一种策略直接选择一个子问题去求解,没被选择的子问题就不去求解了,直接抛弃。也就是说,它总是只在上一步选择的基础上继续选择,因此整个过程以一种单链的流水方式进行。而动态规划总是从边界开始向上得到目标问题的解。也就是说,它总是会考虑所有子问题,并选择继承能得到最优结果的那个,对暂时没被继承的子问题,由于重叠子问题的存在,后期可能会再次考虑它们,因此还有机会成为全局最优的一部分,不需要放弃。    

  • 相关阅读:
    AGC 018E.Sightseeing Plan(组合 DP)
    BZOJ.4767.两双手(组合 容斥 DP)
    AGC 001E.BBQ Hard(组合 DP)
    洛谷.3960.列队(线段树/树状数组)
    Codeforces Round #514 (Div. 2)
    10.4 正睿国庆集训测试 青岛
    Codeforces.264E.Roadside Trees(线段树 DP LIS)
    BZOJ.4653.[NOI2016]区间(线段树)
    Ansible安装部署以及常用模块详解
    Linux系统诊断必备技能之二:tcpdump抓包工具详解
  • 原文地址:https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes26.html
Copyright © 2011-2022 走看看