算法学习笔记(八) 动态规划的通常求解方法

1 一个问题:换零钱方式的统计

SICP 第一章 1.2.2 树形递归中,有这么一问题:给了半美圆,四分之中的一个美圆。10美分,5美分和1美分的硬币。将1美圆换成零钱,一共同拥有多少种不一样方式?更通常的问题是,给定了随意数量的现金,咱们能写一个程序,计算出所有换零钱方式的种数吗?python


2 动态规划的基本模型

动态规划(Dynamic programming,DP),是研究一类最优化问题的方法,经过把原问题分解为相对简单的子问题的方式求解复杂问题。动态规划处理的也就是是多阶段决策最优化问题,这一类问题可将过程分红若干个互相联系的阶段,在每一阶段都做出决策,从而使整个过程达到最好的结果。

所以各个阶段决策的选取不能随意肯定,它依赖于当前面临的状态。又影响之后的发展。当各个阶段决策肯定后,就组成一个决策序列,从而也就肯定了整个过程的一条活动路线。这样的把一个问题看作是一个先后关联具备链状结构的多阶段过程称为多阶段决策过程。算法

动态规划著名的应用实例有:求解最短路径问题,背包问题,项目管理,网络流优化等。动态规划的基本模型例如如下:
网络

  1. 肯定问题的决策对象
  2. 对决策过程划分阶段
  3. 对各阶段肯定状态变量
  4. 依据状态变量肯定费用函数和目标函数
  5. 创建各阶段状态变量的转移过程。肯定状态转移方程

3 使用动态规划的通常前提

3.1 知足动态规划的最优化原理

做为整个过程的最优策略具备例如如下性质:无论过去的状态和决策怎样,对前面的决策所造成的当前状态而言,余下的诸决策必须构成最优策略。


通俗理解就是子问题的局部最优将致使整个问题的全局最优,即问题具备最优子结构的性质,也就是说一个问题的最优解仅仅取决于其子问题的最优解,非最优解对问题的求解没有影响。函数


3.2 知足动态规划的无后效性原则

所谓无后效性原则。指的是这样一种性质:某阶段的状态一旦肯定,则此后过程的演变再也不受此前各状态及决策的影响。也就是说,“将来与过去无关”,当前的状态是此前历史的一个完整总结,此前的历史仅仅能经过当前的状态去影响过程将来的演变。

详细地说,假设一个问题被划分各个阶段以后,阶段 I 中的状态仅仅能由阶段 I+1 中的状态经过状态转移方程得来,与其它状态没有关系,特别是与未发生的状态没有关系,这就是无后效性。从图论的角度去考虑。假设把这个问题中的状态定义成图中的顶点,两个状态之间的转移定义为边,转移过程当中的权值增量定义为边的权值,则构成一个有向无环加权图,所以,这个图可以进行“拓扑排序”,至少可以按他们拓扑排序的顺序去划分阶段。优化


4 动态规划设计方法

4.1 通常方法

通常 由初始状态開始。经过对中间阶段决策的选择,达到结束状态。这些决策造成了一个决策序列,同一时候肯定了完毕整个过程的一条活动路线。步骤为:
  1. 划分阶段:依照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时。注意划分后的阶段必定要是有序的或者是可排序的。不然问题就没法求解。
  2. 肯定状态和状态变量:将问题发展到各个阶段时所处于的各类客观状况用不一样的状态表示出来。固然,状态的选择要知足无后效性。

  3. 肯定决策并写出状态转移方程:因为决策和状态转移有着自然的联系。状态转移就是依据上一阶段的状态和决策来导出本阶段的状态。

    因此假设肯定了决策,状态转移方程也就可写出。但其实常常是反过来作。依据相邻两段各状态之间的关系来肯定决策。this

  4. 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
  5. 程序设计实现:动态规划的主要难点在于理论上的设计。一旦设计完毕,实现部分就会很easy。

4.2 逆向推导

逆向思惟法是指从问题目标状态出发倒推回初始状态或边界状态的思惟方法。假设原问题可以分解成几个本质一样、规模较小的问题,很是天然就会联想到从逆向思惟的角度寻求问题的解决。动态规划与分治法最大的不一样在于分解出来的各个子问题的性质不一样:
  • 分治法要求各个子问题是独立的(即不包括公共的子问题),所以一旦递归地求出各个子问题的解后,即可自下而上地将子问题的解合并成原问题的解。假设各子问题是不独立的,那么分治法就要作不少没必要要的工做,反复地解公共的子问题。
  • 动态规划与分治法的不一样之处在于动态规划赞成这些子问题不独立(即各子问题可包括公共的子问题),它对每个子问题仅仅解一次,并将结果保存起来,避免每次碰到时都要反复计算。这就是动态规划高效的一个缘由。
动态规划的逆向推导步骤:
  1. 分析最优值的结构。刻画其结构特征;
  2. 递归地定义最优值;
  3. 按自底向上或自顶向下记忆化的方式计算最优值;

4.3 正向推导

正向思惟法是指从初始状态或边界状态出发。利用某种规则不断到达新的状态,直到问题目标状态的方法。

动态规划的正向思惟法。正是从已知最优值的初始状态或边界状态開始,依照必定的次序遍历整个状态空间,递推出每个状态所相应问题的最优值。spa


正向思惟法中。再也不区分原问题和子问题,将动态规划的过程当作是从状态到状态的转移。将所有的状态构造出一个状态空间,并在状态空间中设想一个状态网络。若对两个状态i,j,存在决策变量di使t(i,di)=j。则向状态网络加入有向边。.net

给定己知最优值的初始状态或边界状态,可以沿著有向边推广到未知最优值的新状态。利用状态转移方程获得新状态的状态变量的最优值。设计

咱们可以用这样的方式遍历整个状态空间,获得每个状态的状态变量的最优值。
动态规划的正向推导步骤:
code

  1. 构造状态网络;
  2. 依据状态转移关系和状态转移方程创建最优值的递推计算式:
  3. 按阶段的前后次序计算每个状态的最优值;

动态规划需要按阶段遍历整个状态空间。所以动态规划的效率取决于状态空间的大小和计算每个状态最优值的开销:假设状态空间的大小是多项式的,那么应用动态规划的算法就是多项式时间的;假设状态空间的大小是指数的,那么应用动态规划的算法也是指数时间的。所以,找一个好的状态划分对动态规划的效率是相当重要的。


5 小实验换零钱问题求解

逆推状态转移方程:数量为 a 的钱换成 n 种硬币的不一样方式等于:
  • 数量为 a 的钱换成除第一种硬币外的 n-1 种硬币(必不包括第一种硬币)的不一样方式数目加上,
  • 数量为 a-d 的钱(必包括第一种硬币)换成所有硬币种类的不一样方式数目。d 为第一种硬币币值
逆推到初始状态:
  • 假设钱为 0 ,说明正好换完成,是一种换零钱方法,
  • 假设钱为负数,或者种类已经递归到 0 种,则说明没有正好换完,不是一种换法,返回0;

还可以正向推导。打表记录已经计算出的值。

Python实现

NUM = 0


def count_change(amount, money, kinds):
    ''' 树形递归存在冗余'''
    global NUM
    if amount == 0:
        NUM+=1
        return 1
    if amount < 0 or kinds == 0:
        NUM+=1
        return 0
    NUM+=1
    return count_change(amount, money, kinds - 1) + count_change(amount - money[kinds - 1], money, kinds)

def count_dy(amount,money,kinds):
    '''动态规划,打表记录已经计算的值'''
    table = [[0 for col in range(kinds)] for row in range(amount+1)]
    table[0] = [1]*kinds
    for i in range(1,amount+1):
        for j in range(kinds):
            # 包括 money[j]
            x = table[i - money[j]][j] if i-money[j] >= 0 else 0
            # 不包括 money[j]
            y = table[i][j-1] if j>=1 else 0
            table[i][j] = x+y
    return table[amount][kinds-1]

if __name__ == '__main__':
    money = [1, 5, 10, 25, 50]
    print(count_change(100, money, len(money)),'time:',NUM)
    print(count_dy(100, money, len(money)),'time:',100*len(money))

'''
292 time: 15499
292 time: 500
'''

SICP中的Scheme实现(Racket)

【地址:http://blog.csdn.net/thisinnocence/article/details/41073275】

相关文章
相关标签/搜索