动态规划

算法思想

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

算法优势

针对每个状态只须要进行一次运算,以后就能够重复利用这个状态的值,从而减小了大量没必要要的重复计算。也就是,一旦出现重复的子问题求解,优先考虑动态规划方式求解,通常都会得到不少优化算法

算法特色

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

动态规划条件

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

求解步骤

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

算法实例

问题1:斐波那契数列

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

Sequence: 112358132134,……

状态转移方程:优化

详细代码spa

#!/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:收益最大

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

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

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

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   # 指望结果

详细代码utf-8

#!/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:不相邻元素总和最大

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

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("\n") #打印二维数组的值 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就是他们的最长公共子序列。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息