动态规划算法详解及经典例题

1、基本概念

(1)一种使用多阶段决策过程最优的通用方法。java

(2)动态规划过程是:每次决策依赖于当前状态,又随即引发状态的转移。一个决策序列就是在变化的状态中产生出来的,因此,这种多阶段最优化决策解决问题的过程就称为动态规划。算法

    假设问题是由交叠的子问题所构成,咱们就可以用动态规划技术来解决它。通常来讲,这种子问题出自对给定问题求解的递推关系中,这个递推关系包括了一样问题的更小子问题的解。动态规划法建议,与其对交叠子问题一次从新的求解,不如把每个较小子问题仅仅求解一次并把结果记录在表中(动态规划也是空间换时间的)。这样就可以从表中获得原始问题的解。数组

(3)动态规划常常常使用于解决最优化问题,这些问题多表现为多阶段决策。测试

    关于多阶段决策:在实际中,人们常常遇到这样一类决策问题,即由于过程的特殊性,可以将决策的全过程根据时间或空间划分若干个联系的阶段。而在各阶段中。人们都需要做出方案的选择。咱们称之为决策。并且当一个阶段的决策以后,常常影响到下一个阶段的决策,从而影响整个过程的活动。这样,各个阶段所肯定的决策就构成一个决策序列,常称之为策略。由于各个阶段可供选择的决策每每不止一个。于是就可能有不少决策以供选择,这些可供选择的策略构成一个集合,咱们称之为赞成策略集合(简称策略集合)。每个策略都对应地肯定一种活动的效果。咱们假定这个效果可以用数量来衡量。 由于不一样的策略常常致使不一样的效果,所以,怎样在赞成策略集合中选择一个策略,使其在预约的标准下达到最好的效果。常常是人们所关心的问题。咱们称这种策略为最优策略,这类问题就称为多阶段决策问题。优化

(4)多阶段决策问题举例:机器负荷分配问题spa

    某种机器可以在高低两种不一样的负荷下进行生产。在高负荷下生产时。产品的年产量g和投入生产的机器数量x的关系为g=g(x),这时的年完善率为a,即假设年初完善机器数为x,到年终时完善的机器数为a*x(0<a<1);在低负荷下生产时,产品的年产量h和投入生产的机器数量y的关系为h=h(y)。对应的完善率为b(0<b<0)。且a<b。设计

    假定開始生产时完善的机器熟练度为s1。code

    要制定一个五年计划,肯定每一年投入高、低两种负荷生产的完善机器数量,使5年内产品的总产量达到最大。blog

    这是一个多阶段决策问题。排序

    显然可以将全过程划分为5个阶段(一年一个阶段),每个阶段開始时要肯定投入高、低两种负荷下生产的完善机器数,而且上一个阶段的决策一定影响到下一个阶段的生产状态。决策的目标是使产品的总产量达到最大。这个问题经常使用数学方法建模,结合线性规划等知识来进行解决。

2、基本思想与策略


  基本思想与分治法相似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了实用的信息。

       在求解任一子问题时,列出各类可能的局部解,经过决策保留那些有可能达到最优的局部解,丢弃其它局部解。依次解决各子问题,最后一个子问题就是初始问题的解。由于动态规划解决的问题多数有重叠子问题这个特色。为下降反复计算。对每一个子问题仅仅解一次,将其不一样阶段的不一样状态保存在一个二维数组中。

       与分治法最大的区别是:适合于用动态规划法求解的问题,经分解后获得的子问题每每不是互相独立的(即下一个子阶段的求解是创建在上一个子阶段的解的基础上,进行进一步的求解)。

3、适用的状况


能采用动态规划求解的问题的通常要具备3个性质:

(1)最优化原理:假设问题的最优解所包括的子问题的解也是最优的,就称该问题具备最优子结构,即知足最优化原理。

(2)无后效性:即某阶段状态一旦肯定。就不受这个状态之后决策的影响。也就是说,某状态之后的过程不会影响曾经的状态。仅仅与当前状态有关;

(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被屡次使用到(该性质并不是动态规划适用的必要条件,但是假设没有这条性质。动态规划算法同其它算法相比就不具有优点)。

4、求解的基本步骤

     动态规划所处理的问题是一个多阶段决策问题,通常由初始状态开始,经过对中间阶段决策的选择,达到结束状态。这些决策造成了一个决策序列,同时肯定了完成整个过程的一条活动路线(一般是求最优的活动路线)。如图所示。动态规划的设计都有着必定的模式,通常要经历如下几个步骤。

    初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

                 

    (1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段必定要是有序的或者是可排序的,不然问题就没法求解。

    (2)肯定状态和状态变量:将问题发展到各个阶段时所处于的各类客观状况用不一样的状态表示出来。固然,状态的选择要知足无后效性。

    (3)肯定决策并写出状态转移方程:由于决策和状态转移有着自然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。因此若是肯定了决策,状态转移方程也就可写出。但事实上经常是反过来作,根据相邻两个阶段的状态之间的关系来肯定决策方法和状态转移方程。

    (4)寻找边界条件:给出的状态转移方程是一个递推式,须要一个递推的终止条件或边界条件。

    通常,只要解决问题的阶段、状态和状态转移决策肯定了,就能够写出状态转移方程(包括边界条件)。

实际应用中能够按如下几个简化的步骤进行设计:

    (1)分析最优解的性质,并刻画其结构特征。

    (2)递归的定义最优解。

    (3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值。

    (4)根据计算优值时获得的信息,构造问题的最优解。

5、常见动态规划问题

 一、找零钱问题   

   有数组penny,penny中全部的值都为正数且不重复。每一个值表明一种面值的货币,每种面值的货币可使用任意张,再给定一个整数aim(小于等于1000)表明要找的钱数,求换钱有多少种方法。给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法能够凑成aim。

测试样例:
penny=[1,2,4]

penny_size=3

aim = 3
返回:2
即:方案为{1,1,1}和{1,2}两种

分析:

  设dp[n][m]为使用前n中货币凑成的m的种数,那么就会有两种状况:

              使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]]

              不用第n种货币:dp[n-1][m],为何不使用第n种货币呢,由于penney[n]>m。

       这样就能够求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]],

  不然,dp[n][m] = dp[n-1][m]。

import java.util.*;  
public class Exchange {  
    public int countWays(int[] penny, int n, int aim) {  
        // write code here  
        if(n==0||penny==null||aim<0){  
         return 0;     
        }  
        int[][] pd = new int[n][aim+1];  
        for(int i=0;i<n;i++){  
         pd[i][0] = 1;     
        }  
        for(int i=1;penny[0]*i<=aim;i++){  
         pd[0][penny[0]*i] = 1;     
        }  
        for(int i=1;i<n;i++){  
            for(int j=0;j<=aim;j++){  
                if(j>=penny[i]){  
                    pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];  
                }else{  
                    pd[i][j] = pd[i-1][j];  
                }  
            }  
        }  
        return pd[n-1][aim];  
    }   

二、走方格问题


      有一个矩阵map,它每一个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上全部的数字累加起来就是路径和,返回全部的路径中最小的路径和。
给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100.
测试样例:
[[1,2,3],[1,1,1]],2,3
返回:4

解析:设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

import java.util.*;    
public class MinimumPath {  
    public int getMin(int[][] map, int n, int m) {  
        // write code here  
       int[][] dp = new int[n][m];  
        for(int i=0;i<n;i++){  
            for(int j=0;j<=i;j++){  
             dp[i][0]+=map[j][0];      
            }  
        }  
        for(int i=0;i<m;i++){  
            for(int j=0;j<=i;j++){  
             dp[0][i]+=map[0][j];      
            }  
        }  
        for(int i=1;i<n;i++){  
            for(int j=1;j<m;j++){  
             dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);     
            }  
        }  
        return dp[n-1][m-1];  
    }  
    public int min(int a,int b){  
        if(a>b){  
         return b;     
        }else{  
         return a;     
        }  
    }  
相关文章
相关标签/搜索