动态规划的理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xr469786706/article/details/87900652

动态规划的理解

  1. 什么是动态规划

    动态规划是一种非常精妙的算法思想,它没有固定的写法,极其灵活,常常需要具体问题具体分析。和之前介绍的大部分算法不同,一开始就直接讨论动态规划的概念并不是很好的学习方式,反而先接触一些经典模型会有更好的效果。因此本章主要介绍一些动态规划的经典模型,并在其中穿插动态规划的概念,让读者慢慢接触动态规划。同时请读者不要畏惧,多训练,多思考,多总结是学习动态规划的重点。

    动态规划(Dynamic Programming, DP) 是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题时,就可以直接使用之前记录的结果,而不是重复计算。注意:虽然动态规划采用这种方式来提高计算效率,但不能说这种做法就是动态规划的核心(后面会说明这一点)。

    一般可以使用递归或者递推的写法来实现动态规划,其中递归写法在此处又称作记忆化搜索。

  2. 什么问题可以使用动态规划解决

    如果一个问题可以被分解成若干个子问题,且这些子问题会重复出现,那么就称这个问题拥有重叠子问题(Overlapping Subproblems)。动态规划通过记录重叠子问题的解,来使下次碰到相同的子问题时直接使用之前记录的结果,以此避免大量重复计算。因此,一个问题必须拥有重叠子问题,才能使用动态规划去解决。

  3. 动态规划的递推写法

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

    dp[i][j]表示从第i行第j个数字出发的到达最底层的所有路径中能得到的最大和,例如dp[3][2]就是图中的7到达最底层的路径最大和。在定义这个数组之后,dp[1][1]就是最终想要的答案,现在想办法求出它。

    注意到:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+f[i][j]

    dp[i][j称为问题的状态,而把上面的式子称作状态转移方程,可以发现,数塔的最后一层的dp值总是等于元素本身,即dp[n][j] == f[n][j](1<=j<=n),把这种可以直接确定其结果的部分称为边界,而动态规划的递推写法总是从这些边界出发,通过状态转移方程扩散到整个dp数组。

    这样就可以从最底层各位置的dp值开始,不断往上求出每一层各位置的dp值,最后就会得到dp[1][1],即为想要的答案。

  4. 算法代码

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    
    using namespace std;
    
    const int maxn = 1000;
    
    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 ; j <= n ; j++)
            dp[n][j] = f[n][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];
            }
        }
        printf("%d\n",dp[1][1]);	// dp[1][1]即为需要的答案
        return 0;
    }
    

    如果一个问题的最优解可以由其子问题的最优解有效地构造出来,那么称这个问题拥有最优子结构(Optimal Substructure)。最优子结构保证了动态规划中原问题的最优解可以由子问题的最优解推导出来。因此,一个问题必须拥有最优子结构,才能使用动态规划去解决。

    至此,重叠子问题和最优子结构的内容已介绍完毕。需要指出,一个问题必须拥有重叠子问题和最优子结构,才能使用动态规划去解决。

  5. 注意:和分治法,贪心算法的比较

    1. 分治法与动态规划。分治和动态规划都是将问题分解为子问题,然后合并子问题的解得到原问题的解。但是不同的是,分治法分解出的子问题是不重叠的,因此分治法解决的问题不拥有重叠子问题,而动态规划解决的问题拥有重叠子问题。例如,归并排序和快速排序都是分别处理左序列和右序列,然后将左右序列的结果合并,过程中不出现重叠子问题,因此它们使用的都是分治法。另外,分治法解决的问题不一定是最优化问题,而动态规划解决的问题一定是最优化问题。
    2. 贪心与动态规划。贪心和动态规划都要求原问题必须拥有最优子结构。二者的区别在于,贪心法采用的计算方式类似于上面介绍的”自顶而下“,但是并不等待子问题求解完毕后再选择使用哪一个,而是通过一种策略直接选择一个子问题去求解,没被选择的子问题就不去求解了,直接抛弃。也就是说,它总是只在上一步选择的基础上继续选择,因此整个过程以一种单链的流水方式进行,显然这种所谓"最优选择"的正确性需要用归纳法证明。例如对数塔问题而言,贪心法从最上层开始,每次选择左下和右下两个数字中较大的一个,一直到最底层得到最后结果,显然这不一定可以得到最优解。而动态规划不管是采用自底而上还是自顶而下的计算方式,都是从边界开始向上得到目标问题的解。也就是说,它总是会考虑所有子问题,并选择继承能得到最优结果的那个,对暂时没被继承的子问题,由于重叠子问题的存在,后期可能会再次考虑它们,因此还有机会成为全局最优的一部分,不需要放弃。所以贪心是一种壮士断腕的决策,只要进行了选择,就不后悔;动态则要看哪个选择笑到了最后,暂时领先说明不了什么。

猜你喜欢

转载自blog.csdn.net/xr469786706/article/details/87900652
今日推荐