动态规划

一 、动态规划

如果要求解一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且子问题之间也存在重叠的子问题,则考虑采用动态规划。 动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到了。

使用动态规划特征: 
1. 求一个问题的最优解 
2. 大问题可分解为子问题,子问题还有重叠的更小的子问题 
3. 填表:整体问题最优解取决于子问题的最优解状态转移方程,即:当前状态是怎么由上一个状态来的) ,而子问题最优解可通过查表得到。边查表边填表。
4. 从上往下分析问题,从下往上解决问题 。(当然也可以采用带备忘的自顶向下的解决方法,复杂度系数比前者稍大。)
5. 讨论底层的边界问题并对其初始化

例题1:硬币找零问题

假设有面值为1元3元5元的硬币若干枚(或数量无限),请找出能组成某个数目的找零所使用最少的硬币数,如:

怎样用最少的硬币凑够11元? 

分析: 
1 求问题的最优解:最少的硬币数 
2 是否有子问题状态):f(n)表示的最少硬币数是上一次拿时候的硬币数最少。 
注意:f(n)是n元的最小硬币数,最后一次可拿的硬币数为1,3,5 则下一步的最小硬币数为 f(n-h[i]) +1,其中h[i]有1,3,5。可见它的状态变更不是按元数的,而是按照上次拿的硬币面值
3 状态转移方程为 f(n)= min(f(n-h[i])+1) 
4 底层边界问题(找到最后一个重复的问题) : f(1)=1 ,f(2)=f(1)+f(1)=2 f(3)=1  f(4)=f(3)+1 f(5)=1 
5 从上往下分析问题,从下往上解决问题

#自底向上解决子问题,且将子问题的解保存下来,从而避免重复计算子问题!!!
def f(n):
    if n == 1:#5个边界问题
        return 1
    if n == 2:
        return 2
    if n == 3:
        return 1
    if n == 4:
        return 2
    if n == 5:
        return 1
    record=[0]*(n+1)#用来保存子问题的最优解
    record[1],record[2],record[3],record[4],record[5]=1,2,1,2,1
    h=[1,3,5]
    for i in range(6,n+1):#从下往上的思维解决
        #record[i]=min(record[i-1],record[i-3],record[i-5])+1
        minx=n
        for j in range(3):
            record[i]=record[i-h[j]]+1#状态转移方程。直接查表,使用已存在表中的子问题的解,而不是利用递归来重新计算子问题
            if minx > record[i]:
                minx=record[i]
        record[i]=minx
    return record[n]

print( f(11) )

扩展1:一个矩形区域被划分为N*M个小矩形格子,在格子(i,j)中有A[i][j]个苹果。现在从左上角的格子(1,1)出发,要求每次只能向右走一步或向下走一步,最后到达(N,M),每经过一个格子就把其中的苹果全部拿走。请找出能拿到最多苹果数的路线。

分析:题中,当前位置(i,j)是状态,用M[i][j]表示到达状态(i,j)所能得到的最多苹果数,则M[i][j] = max(M[i-1][j],M[i][j-1]) + A[i][j] 。边界情况是M[1][1]=A[1][1],当i=1且j!=1时,M[i][j] = M[i][j-1] + A[i][j];当i!=1且j=1时M[i][j] = M[i-1][j] + A[i][j]。

扩展2:三次摘苹果(难度系数:3星)

一个矩形区域被划分为N*M个小矩形格子,在格子(i,j)中有A[i][j]个苹果。现在从左上角的格子(1,1)出发,要求每次只能向右走一步或向下走一步,每经过一个格子就把其中的苹果全部拿走,最后到达(N,M)。此时,只允许向上或向左走一步,反方向走回(1,1)。这一趟可以不走第一趟的路线,但当经过第一趟所经过的格子时,里面已经没有苹果了。到达(1,1)后,再次反方向地只允许向右或向下走,走到(N,M),同样可以不走前两趟走过的路线。求这三趟的走法,使得最终能拿取最多数量的苹果。

解法:问题有两个难点,首先要理清三条路线的关系。可发现,虽然第二趟方向相反,但其实和从(1,1)走到(N,M)是一样的,即三趟路线全部可以转化为从(1,1)向下或向右走到(N,M)的过程。
  观察三条路线可发现,走的时候如果路线有交叉,可把这种情况转化为相遇而不交错的情况如下图:

这样做的好处是,对于红线和蓝线上同一行j的点坐标(i,j)(i',j),总有i<=i',这样就能把三条路线划分成左、中、右三条有序的路线。

  经过两次转化,可以构造子结构了。用Max[y-1][i][j][k]表示在y-1行时,三条路线分别在i、j、k时所能取得的最大苹果数,用Max[y-1][i][j][k]可求解任意的Max[y][i'][j'][k'],其中i' = i to j' , j' = j to k', k' = k to M. 若线路重叠,则苹果已被取走,不用重复考虑。因此处理每一行时应使用一个该位置苹果是否被取走的标志位。根据上面的范围关系,先求k'的所有情况,然后是j',最后才是i'。这样Max[N][M][M][M]就是三趟后所能取得的最多苹果数。

例题2:剪绳子问题 

给你一根长度为N的绳子,请把绳子剪成M段(m,n都是整数),每段绳子的长度记为k[0],k[1],k[2]…. 请问如何剪绳子使得k[0],k[1],k[2] 的乘积最大 。例如 绳子长度8 最大乘积18 = 2*3*3。代码如下:

# -*- coding: utf-8 -*-
def cutropes(n):
    # 先对边界问题进行求解,因为明显剪的值小于不剪的值
    # 则提前先讨论这三种情况
    if n < 2:
        return 0
    if n == 2:
        return 1    #长度为2,只能剪成1*1
    if n == 3:
        return 2    #长度为3,剪成2*1 > 1*1*1

    #若绳子长于4呢,申请一个长度为5的数组
    #罗列出切割的边界问题!!!!!
    h = [0]*(n+1)#申请一个长度为n+1的数组!保存每个状态对应的子问题的解
    h[0] = 0
    h[1] = 1
    h[2] = 2
    h[3] = 3
    # 递归问题是:f(n) = max{f(i)*f(n-i)}
    for i in range(4,n+1): # i为剪切前的绳子长度
        maxs = 0
        for j in range(1,i//2+1):#长度为i的绳子剪成两段时,较长的那段的最大值为 i//2(i为偶数时)或i//2+1(i为奇数时)。
            mult = h[j] * h[i-j] #直接查看子问题的最优解,用来求解状态转移方程:f(n) = max{f(i)*f(n-i)}
            if maxs < mult:
                maxs = mult
        h[i] = maxs     # J迭代轮询结束后得出长度为i的最大值
    print (h)
    return h[n]

print (cutropes(8))

例题3:矩阵链乘法

 一个给定的矩阵序列A1A2...An计算连乘乘积,矩阵乘法满足结合律,不满足交换律,只能相邻结合。根据矩阵乘法公式,10*100和100*5的矩阵相乘需做10*100*5次乘法。则对于维数分别为10*100、100*5、5*50的矩阵A、B、C,用(A*B)*C计算需要10*100*5 + 10*5*50 =7500次乘法;而A*(B*C)则需要100*5*50+10*100*50=75000次标量乘法。那么对于由n个矩阵构成的链<A1,A2,...,An>,对i=1,2,...n,矩阵Ai的维数为pi-1*pi,对乘积A1A2...An求出最小化标量乘法的加括号方案。

解法:尽管可用递归计算取1<=k<n使得P(n)=∑P(k)P(n-k),遍历所有P(n)种方案,但这不是一个高效的解法。很容易看出,子结构(状态)是求Ai...Aj的加括号方法m[i][j],可递归地定义为:当i=j时,m[i][j]=0,当i<j时,m[i][j]=m[i][k]+m[k+1][j]+pi-1*pk*pj

# -*- coding: utf-8 -*-
import numpy as np
def matrix_mult(p):#p=[p0,p1,p2,...pn]
    n=len(p)-1 #n为矩阵链的长度,即矩阵的个数
    m=[]
    s=[]
    #因为数组下标从0开始,而题目给的矩阵下标从1开始,所以用m[i][j]代表Ai+1...Aj+1的最少标量乘法次数,如m[0][1]代表A1A2的最少标量乘法次数
    for ii in range(n):
        m.append([0]*n)#转成二维矩阵,m[i][j]记录标量乘法的最少次数
        s.append([0]*n)#转成二维矩阵

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值