zoukankan      html  css  js  c++  java
  • 动态规划

    算法思想

    将待求解的问题分解为若干个子问题,按顺序求解子问题,同时前一子问题的解,为后一子问题的求解提供了有用的信息。

    算法优点

    针对每一个状态只需要进行一次运算,之后就可以重复利用这个状态的值,从而减少了大量不必要的重复计算。也就是,一旦出现重复的子问题求解,优先考虑动态规划方式求解,一般都会获得很多优化

    算法特点

    • 求解子问题时只保存针对当前子问题的最优解
    • 重叠子问题,减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个数组中。
    • 子问题往往不是互相独立的

    动态规划条件

    • 总问题可划分为多个子问题
    • 最优化原理,通过子问题的最优解可以获得整个问题的最终最优解
    • 无后效性。当前状态的子问题的解不会被之后状态的子问题影响
    • 子问题的重叠性。动态规划的子问题不独立,单个子问题的最优解可以使用之前已解决的子问题的解,解决冗余,以空间换时间的技术,这也是动态规划的优点

    求解步骤

    • 子问题的划分。 按照一定的顺序把整个问题划分为若干个规模相等的子问题
    • 子问题状态的确定。根据问题需求和子问题的各个属性确定子问题的”状态“,同时需要满足无后效性。
    • 推导状态转移方程。 状态转移指的就是根据上一个状态(或者叫上一个子问题的解)来获取当前子问题解的过程
    • 边界条件和初始值的确定。由于动态规划是根据之前子问题的解来推导当前子问题的解,所以最初状态的值必须确定。边界条件是用来描述结束状态的,如果当前状态完全到达边界,便视为已经到达了最终状态。

    算法实例

    问题1:斐波那契数列

    数列由1,1开始,之后的斐波那契数列系数就由之前的两数相加。

    Sequence: 112358132134,……

    状态转移方程:

    详细代码

    #!/usr/bin/python
    #-*-coding:utf-8 -*-
    
    import numpy as np
    
    #递归方式
    def rec_fib(n):
        if(n == 0):
            return 0
        elif(n == 1):
            return 1
        else:
            fib = rec_fib(n-1) + rec_fib(n-2)
        return fib
    
    #使用变量存储中间结果
    def dp_fib(n):
        a, b = 0, 1
        while(n):
            a, b = b, a+b
            n -= 1
        return a
    
    if __name__ == '__main__':
        print(rec_fib(6))
        print(dp_fib(6))   

    问题2:收益最大

    纵轴是所作的任务编号,以及收益;横轴是任务所对应的时间。选取规则为任务时间不能重叠,使得收益最大。

    任务编号:       12345678
    
    任务对应收益v(i):51846324

    第i个任务选状态(选和不选)最优解opt(i):

    pre(i)为之前的状态,对应任务编号的先前状态表:

    i:       1  2  3  4  5  6  7   8             
    pre(i):  0  0  0  1  0  2  3   5    # 任务编号的先前状态表           
    opt(i):  5  5  8  9  9  9  10  13   # 期望结果

    详细代码

    #!/usr/bin/python
    #-*-coding:utf-8 -*-
    
    import numpy as np
    
    pre = [0, 0, 0, 1, 0, 2, 3, 5]
    arr = [1, 2, 3, 4, 5, 6, 7, 8]
    V  =  [5, 1, 8, 4, 6, 3, 2, 4]
    
    # 递归方式
    def rec_values(arr, n):
        if(n == 0):
            return 0
        else:
            A = V[n - 1] + rec_values(arr, pre[n - 1])
            B = rec_values(arr, n - 1)
        return max(A, B)
    
    # 数组存放中间值
    def dp_values(arr):
        opt = np.zeros(len(arr) + 1)
        opt[0] = 0
        for i in range(1, len(arr) + 1):
            A = V[i - 1] + opt[pre[i - 1]]
            B = opt[i - 1]
            opt[i] = max(A, B)
        return opt[len(opt) - 1]
    
    if __name__ == '__main__':
        print(rec_values(arr, 8)) # 13
        print(dp_values(arr)) # 13

    问题3:不相邻元素总和最大

    给定一个数组,选取一些元素使得总和最大,选取规则为不能连续取两个元素,即选取的元素之间至少要间隔一个其它元素,每个元素都有选与不选两种可能,使得用动态规划求解。

    arr: 1,2,4,1,7,8,3

    状态转移方程

    出口条件

    opt[0] = arr[0]             
    opt[1] = max(arr[0], arr[1])

    详细代码

    #!/usr/bin/python
    #-*-coding:utf-8 -*-
    
    import numpy as np
    
    arr = [1, 2, 4, 1, 7, 8, 3]
    
    #递归方式
    def rec_opt(arr, i):
        if(i == 0):
            return arr[0]
        elif(i == 1):
            return max(arr[0], arr[1])
        else:
            A = rec_opt(arr, i-2) + arr[i]
            B = rec_opt(arr, i-1)
            return max(A, B)
    
    #使用数组存储中间结果
    def dp_opt(arr):
       opt = np.zeros(len(arr))
       opt[0] = arr[0]
       opt[1] = max(arr[0], arr[1])
       for i in range(2, len(arr)):
           A = opt[i-2] + arr[i]
           B = opt[i-1]
           opt[i] = max(A, B)
       return opt[len(arr) - 1]
    
    if __name__ == '__main__':
        print(rec_opt(arr, 6))
        print ( dp_opt(arr))

    问题4:寻找和为定值的数

    给定一个数组,选取一些元素使得总和为给定的值。

    arr:  33441252
    s:    9

    状态转移方程

    其中,subset(arr, i, s)表示为数组arr当前第i个数字需要计算的和为s

    出口条件

    if s ==0:                              
        return true                             
    elif i == 0:                                    
        return arr[0] == s                         
    elif arr[i] > s:                           
        return subset(arr, i-1, s)   

    使用二维数组进行存储结果

    arr   i    0  1  2  3  4  5  6  7  8  9        s          
    3     0    F  F  F  F  T  F  F  F  F  F                               
    34    1    T                                               
    4     2    T                                                    
    12    3    T                                                       
    5     4    T                                                      
    2     5    T                                                  

    详细代码

    #!/usr/bin/python
    #-*-coding:utf-8 -*- 
    
    import numpy as np
    
    arr = [3,34,4,12,5,2]
    
    # 递归方式
    def rec_subset(arr,i,s):
        if s == 0:
            return True
        elif i == 0:
            return arr[0]==s
        elif arr[i] > s:
            return rec_subset(arr,i-1,s)
        else:
            A=rec_subset(arr,i-1,s-arr[i])
            B=rec_subset(arr,i-1,s)
            return A or B
    def rec_subset_test():
        print(rec_subset(arr, len (arr)-1, 9))      #True
        print(rec_subset(arr, len (arr)-1, 10))     #True
        print(rec_subset(arr, len (arr)-1, 11))     #True
        print(rec_subset(arr, len (arr )-1, 12))    #True
        print(rec_subset(arr, len (arr)-1, 13 ))    #False
    
    # 二维数组存储结果
    def dp_subset(arr, S):
        subset = np.zeros((len(arr), S+1), dtype = bool)
    
        # 表示对一个二维数组,取该二维数组第一维中的所有数据,第二维中取第0个数据,即取所有行的第1个数据
        subset[:, 0] = True
        # 所有列的第1个数据
        subset[0, :] = False
        subset[0, arr[0]] = True #i=0时,arr[0] = s = 3
    
        for i in range(1, len(arr)):
            for s in range(1, S+1):
                if(arr[i] > s):
                    subset[i, s] = subset[i-1, s]
                else:
                    A = subset[i-1, s-arr[i]]
                    B = subset[i-1, s]
                    subset[i, s] = A or B
        # r,c分别为二维数组的行数和列数   
        r,c = subset.shape
        return subset[r-1, c-1]
    
    def dp_subset_test():
        print(dp_subset(arr, 9))      #True
        print(dp_subset(arr, 10))     #True
        print(dp_subset(arr, 11))     #True
        print(dp_subset(arr, 12))     #True
        print(dp_subset(arr, 13))     #False
    
    # 当.py文件被直接运行时,if下的代码块将被运行;
    # 当.py文件以模块形式被导入时,if下的代码块不被运行。
    if __name__ == '__main__':
        rec_subset_test()
        dp_subset_test()

     问题5:简单01背包

    给定 n 种物品和一个容量为 W 的背包,物品 i 的重量是 wi,其价值为 vi。应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

    6种物品情况:
    v = [8, 10, 6, 3, 7, 2]
    w = [4, 6, 2, 2, 5, 1]
    背包体积W为12

    状态转移方程

    c(i,w)表示选择第i件物品时得到的物品最大总价值。

    出口条件

    if(i==0 or w==0)  return 0
    if(wi>W) return c(i-1)(w)

    使用二维数组进行存储结果

    i, j
    背包容量
    0 1 2 3 4 5 6 7 8 9 10 11 12
    物品编号 0
    1
    2
    3
    4
    5
    6
    0 0 0 0 0 0 0 0 0 0 0 0 0
    0 0 0 0 8 8 8 8 8 8 8 8 8
    0 0 0 0 8 8 10 10 10 10 18 18 18
    0 0 6 6 8 8 14 14 16 16 18 18 24
    0 0 6 6 9 9 14 14 17 17 19 19 24
    0 0 6 6 9 9 14 14 17 17 19 21 24
    0 2 6 8 9 11 14 16 17 19 19 21 24

    详细代码

    #!/usr/bin/python
    #-*-coding:utf-8-*-
    
    import numpy as np
    
    arr = [1, 2, 3, 4, 5, 6] #每个物品编号
    v   = [8, 10, 6, 3, 7, 2] #每个物品价值
    w   = [4, 6, 2, 2, 5, 1] #每个物品重量
    
    W   = 12 #背包总价值
    
    # 动态规划
    def rec_pack(v, w, n, W):
        if(n<=0 or W <= 0):
            return 0
        elif(w[n-1] > W):
            return rec_pack(v, w, n-1, W)
        else:
            A = rec_pack(v, w, n-1, W-w[n-1]) + v[n-1]
            B = rec_pack(v, w, n-1, W)
            return max(A, B)
    
    # 数组存放中间值
    def dp_pack(v, w, n, W):
        V = np.zeros((len(v) + 1, W+1), dtype = np.int32)
        V[:, 0] = 0
        V[0, :] = 0
        for i in range(1, len(v) + 1):
            for j in range(1, W+1):
                if(w[i-1] > j): #物品重量 > 背包重量
                    V[i,j] = V[i-1, j]
                else:
                    A = v[i-1] + V[i-1, j-w[i-1]]
                    B = V[i-1, j]
                    V[i,j] = max(A, B)
        r,c = V.shape
        '''
        for i in range(0, len(v) + 1):
            print("
    ") #打印二维数组的值
            for j in range(1, W+1):
                print("%d "% V[i,j])
        '''
        return V[r-1, c-1]
    
    if __name__ == '__main__':
        print(dp_pack(v, w, 6, 12))     # 24
        print(rec_pack(v, w, 6, 12))    # 24

    问题6:走台阶

    有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法

    问题7:最长公共子序列

    找到两个字符串间的最长公共子序列。假设有两个字符串sudjxidjs和xidjxidpolkj,其中djxidj就是他们的最长公共子序列。

    作者:yusq77

    -------------------------------------------

    Wish you all the best and good health in 2021.

  • 相关阅读:
    触发器
    新登录用户的次日成功的留存率
    获取薪水第二多的
    找到薪水比经理高的员工
    成绩排名
    exists 和 in
    sum+case 计数
    前N个员工的salary累计和
    员工的薪水按照salary进行按照1N的排名,相同salary并列
    洛谷2678 跳石头
  • 原文地址:https://www.cnblogs.com/yusq77/p/10862592.html
Copyright © 2011-2022 走看看