zoukankan      html  css  js  c++  java
  • 动态规划算法入门

    1. 动态规划算法定义:

        动态规划,英文描述为Dynamic programming. 是一种可以把原始问题分解为若干相关联的子解问题,并通过求取和保存子问题的解,获得原问题的解。

        动态规划算法可以解决的问题通常包含如下特征:

    • 重叠子问题
    • 最优子结构

      对于第一个特征,比较容易理解,即分解的若干子问题,包含着重复的解。举例如:斐波那契数列,F(n) = F(n-1) + F(n-2), 求解的F(n-1)的过程中,包含着求解F(n-2)的结果。

          对于第二个特征,参考网上的说法为:

           假设当前决策结果是f[n],则最优子结构就是要让f[n-k]最优,最优子结构性质就是能让转移到n的状态是最优的,并且与后面的决策没有关系,即让后面的决策安心地使用前面的局部最优解的一种性质。

    关键字解读为:  

    •  当前的决策与后面的决策是无关的,  
    •   f[n-k]是最优的,转移到f[n]的状态是最优的

        2. 动态规划算法的一般步骤和难点

      使用动态规划算法解决问题的一般步骤是:

    •  找到问题的最优解的性质,用数学公式或者算法描述
    •  拆解子问题,确定问题的递推结构,保证可以收敛。

      用知乎大神们的总结就是:找到问题的状态描述和状态转移方程。

        3. 动态规划算法的分类和理解

      根据我的理解,以及网上的说法,我把动态规划算法分为三个类别和层次:

    • 简单动态规划算法,即状态方程是用一个维度的变量的描述的,常见的问题如:斐波那契数列,爬台阶问题等

      爬台阶问题问题描述: 有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。

      状态描述: 我们使用变量n表示台阶的级数,F(n)表示n级台阶一共有多少种走法

      状态转移方程与问题分解: 根据每次能跨越的台阶数目:1级台阶或者2级台阶,因为走到N级台阶之前,人一定是处于N-1级台阶或者N-2级台阶。F(n)的走法,一定是n-1级别的台阶的所有的走法和n-2级别台阶的所有走法之和。

      F(n) = F(n-1) + F(n-2);  关于状态的分解,更详细的说明,可以看这篇文章:http://blog.csdn.net/baidu_37107022/article/details/73188963。 作者讲的非常的通俗易懂。佩服这么辛苦的编辑。

         Java的代码实现

    public static int  getSumStep(int n){
            if(n < 1){
                return 0;
            }
            else if(n == 1){
                return 1;
            }
            else if(n == 2){
                return 1;
            } else {
                int f1 = 1;
                int f2 = 1;
                int f = 0;
                for(int i=3; i<=n; ++i){
                    f = f1 + f2;
                    f1 = f2;
                    f2 = f;
                }
                return f;
            }
        }
    • 二维的变量变化的动态规划算法,即最优解和递推关系需要两个维度变量来描述的,比如01背包问题,两个字符串的公共子序列问题

         这类问题通常需要两个维度的变量,状态的描述比较晦涩,不容易理解,递推关系不是很直观。我自己的学习方法是牢记一个例子,这里以01背包问题为例:

    问题描述:有编号分别为a,b,c,d的四件物品,它们的重量分别是2,3,4,5,它们的价值分别是3,4,5,6,现在给你个承重为8的背包,如何让背包里装入的物品具有最大的价值总和?

    编号 a b c d
    w(重量) 2 3 4 5
    v(价值) 3 4 5 6

         

        这类问题我觉得抽象的比较好的一篇文章是这篇文章:

         http://www.cnblogs.com/Christal-R/p/Dynamic_programming.html, 不过我当时是在手机上看到的,好了好久才找到这篇文章。

        作者抽象的实在太好了,我觉得我都没法用语言去写出这么严格的数学公式表达和证明,这里就不赘述了。

        下面写的,仅供自己理解使用, 总结下来就是:

        Xi的取值为0,1 ;表示物品是否选取, i的取值为 1,2,3,4表示a,b,c,d4见物品

        Wi表示物品的重量, w1=2, 表示 a物品的重量为2 

        Vi 表示物品的价值, v3 - 5, 表示物品c的价值为5;

        

          其中n 表示 前 n个物品,这个表述是很重要的,如果是第一次思考这个问题,很多人都会卡在这里,

          m表示背包的重量;

          约束条件: 

         

           递推关系: 

           

          第一个公式表示  n == 0 或者 m == 0 ,  即物品的数量为0 或者背包的重量为0的时候,可以算是起始条件

          第二个公式表示: 表示包的重量小于新增加的物品, 新增加的物品,无法装入,如下图的F(2, 2 ) 表示前两个物品,包的重量2 ,  2  < (w[2] = 3), 此时F(2,2 )= F(1 , 2) = 3;

          第三个公式表示: 包的重量能够容纳w[n],新增加的物品,这个时候,最大的价值就要在 F(n-1, m) 和 F(n-1, m- Wn) + V[n]) 这两个价值中选取了。

          举例如下图打表的 F(4, 8),  因为 8 - (w[n] ,4) > 0 F(4, 8) = max(F(3, 8), F(3,3 ) + v[4]) = 10;

          表的过程如下:

          

          java代码如下:

         

    public static int getMaxValue(int[] wArray, int[] vArray, int bagWeight){
    
            int lenght = wArray.length;
            // init set zero
            // manipulator the talbe
            int [][] result = new int[lenght+1][bagWeight+1];
            int [][] bRecord = new int[lenght+1][bagWeight+1];
    
            for(int i=1; i<= lenght; ++i){
                for(int j=1;j <= bagWeight; ++j){
                    if(j<wArray[i-1]){
                        result[i][j] = result[i-1][j];
                        bRecord[i][j] = 1;
                    }else{
    
                        if( result[i-1][j] > result[i-1][j-wArray[i-1]]+ vArray[i-1]) {
                            result[i][j] = result[i-1][j];
                            bRecord[i][j] = 1;
                        } else{
                            result[i][j] = result[i-1][j-wArray[i-1]]+ vArray[i-1];
                            bRecord[i][j] = 2;
                        }
    
                    }
                }
            }
    
            return result[lenght][bagWeight];
            //return bRecord;
        }

         需要注意的是因为java数组的索引下标为从0,开始,所以

       result[i][j] = result[i-1][j-wArray[i-1]]+ vArray[i-1];

    brecord是记录操作的过程,用于回溯使用,这部分代码,后续实现。
    • 带有额外条件的动态规划问题(这类问题,我暂时还没有学习)

        4.   动态规划与分治法的区别和联系

          分治法是指将问题划分成一些独立地子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。

         动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。

         分治法主要在于子问题的独立性,比如排序算法等, 动态规划算法主要适用于处理 子问题重复性和最优子结构的的问题。  

         目前的理解还比较浅显,只能先这么记录了。

    天道酬勤、地道酬善、人道酬诚、商道酬信、业道酬精。
  • 相关阅读:
    我的第一个可用的Windows驱动完成了
    据说是一种很古老的方法
    起一卦,测今天工作,问题不少
    起一卦,找房子,马上没房子住了
    哈哈哈哈,我竟然发现了个MSDN里面的笔误
    起一卦,看现在我的工程进度怎么样。
    起卦帮同学看工作,应了。
    2012年10月17日帮朋友算得第一卦
    2013年1月13日帮朋友测的第二卦,有些地方没看出来
    bzoj2588 Spoj 10628. Count on a tree
  • 原文地址:https://www.cnblogs.com/xiaotuan/p/7260111.html
Copyright © 2011-2022 走看看