动态规划(Dynamic programming)

是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

什么是动态规划

动态规划(Dynamic Programming)对于子问题重叠的情况特别有效,因为它将子问题的解保存在表格中,当需要某个子问题的解时,直接取值即可,从而避免重复计算!

动态规划是一种灵活的方法,不存在一种万能的动态规划算法可以解决各类最优化问题(每种算法都有它的缺陷)。所以除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,用灵活的方法建立数学模型,用创造性的技巧去求解。

基本策略

基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

动态规划中的子问题往往不是相互独立的(即子问题重叠)。在求解的过程中,许多子问题的解被反复地使用。为了避免重复计算,动态规划算法采用了填表来保存子问题解的方法。

适用问题

那么什么样的问题适合用动态规划的方法来解决呢?

适合用动态规划来解决的问题,都具有下面三个特点:最优化原理、无后效性、有重叠子问题

(1)最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

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

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

这类问题的求解步骤通常如下:

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

(1)划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的

(2)确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后续性

(3)确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程

(4)边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件

算法实现

动态规划三要素:

(1)问题的阶段

(2)每个阶段的状态

(3)相邻两个阶段之间的递推关系

整个求解过程可以用一张最优决策表来描述,最优决策表是一张二维表(行:决策阶段,列:问题的状态)表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。

例如:f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}

1 事例一背包问题

问题描述:假设我们有n种类型的物品,分别编号为1, 2...n。其中编号为i的物品价值为vi,它的重量为wi。为了简化问题,假定价值和重量都是整数值。现在,假设我们有一个背包,它能够承载的重量是Cap。现在,我们希望往包里装这些物品,使得包里装的物品价值最大化,那么我们该如何来选择装的东西呢?注意:每种物品只有一件,可以选择放或者不放。初始化数据为:n=5,w={2,2,6,5,4},v={6,3,5,4,6},Cap=10

解法如下:

A)描述最优解的结构

设子问题:f[i][v]表示允许前i件物品放入容量为v的背包时可以获得的最大价值。注:这里的i从0到5,v从0到10

为了能够得到已经计算过的,更小规模的子问题,我们可以根据当前限重来只考虑第i件物品放或者不放,那么就可以转化为涉及前i-1件物品的问题,

即:

情况1、如果第i件物品不能放(即这个物品的重量直接大于了当前限重v),则问题转化为“前i-1件物品放入容量为v的背包中”,即f[i-1][v];

情况2、如果放第i件物品是可以放也可以不放,则问题转化为:

1)、如果选择不放第i件物品,则问题转化为“前i-1件物品放入容量为v的背包中”,即变大时f[i-1][v];

2)、如果选择放第i件物品,则问题转化为“前i-1件物品放入剩下的容量为v-w[i]的背包中”,此时能获得的最大价值就是f [i-1][v-w[i]]再加上通过放入第i件物品获得的价值v[i]。

则情况2下,f[i][v]的值就是1),2)中最大的那个值。

最优子结构描述如下:当子问题f[i][v]是最优时,其子问题f[i-1][v]和f[i-1][v-w[i]](中的较大者)显然同样也必须是最优的值,不然在情况1或者情况2下总会矛盾。

B) 递归定义最优解的值

根据上面的分析,显然子问题

f[i][v]=f[i-1][v],这时是情况1

f[i][v]=max{f[i-1][v], f[i-1][v-w[i]]+v[i] },这时是情况2。

C)按自底而上的方式计算最优解的值

import numpy as np#行李数n,不超过的重量W,重量列表w和价值列表p
def fun(n,W,w,p):a=np.array([[0]*(W+1)]*(n+1))#依次计算前i个行李的最大价值,n+1在n的基础上进行for i in range(1,n+1):for j in range(1,W+1):if w[i-1]>j:a[i,j]=a[i-1,j]else:a[i,j]=max(a[i-1,j],p[i-1]+a[i-1,j-w[i-1]])#2种情况取最大值#print(a)print('max value is'+str(a[n,W]))findDetail(p,n,a[n,W])
#找到价值列表中的一个子集,使得其和等于前面求出的最大价值,即为选择方案
def findDetail(p,n,v):a=np.array([[True]*(v+1)]*(n+1))for i in range(0,n+1):a[i][0]=Truefor i in range(1,v+1):a[0][i]=Falsefor i in range(1,n+1):for j in range(1,v+1):if p[i-1]>j:a[i,j]=a[i-1,j]else:a[i,j]=a[i-1,j] or a[i-1,j-p[i-1]]if a[n,v]:i=nresult=[]while i>=0:if a[i,v] and not a[i-1,v]:result.append(p[i-1])v-=p[i-1]if v==0:breaki-=1print(result)else:print('error')
weights=[1,2,5,6,7,9]
price=[1,6,18,22,28,36]
fun(len(weights),13,weights,price)

2 事例二

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

分析:动态规划的实现的关键在于能不能准确合理的用动态规划表来抽象出 实际问题。在这个问题上,我们让f(n)表示走上n级台阶的方法数。

那么当n为1时,f(n) = 1,n为2时,f(n) =2,就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为2。那么当我们要走上n级台阶,必然是从n-1级台阶迈一步或者是从n-2级台阶迈两步,所以到达n级台阶的方法数必然是到达n-1级台阶的方法数加上到达n-2级台阶的方法数之和。即f(n) = f(n-1)+f(n-2),我们用dp[n]来表示动态规划表,dp[i],i>0,i<=n,表示到达i级台阶的方法数。

class Solution:"""@param n: an integer@return: an ineger f(n)"""def up(self, n):# write your code here# if n == 0:#     return 0L = []L.append(1)L.append(2)for i in range(2, n):L.append(L[i - 1] + L[i - 2])return L[n - 1]

3 事例三

给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径1,3,1,0,6,1,0就是最小路径和,返回12.

1 3 5 9

8 1 3 4

5 0 6 1

8 8 4 0

分析:对于这个题目,假设m是m行n列的矩阵,那么我们用dp[m][n]来抽象这个问题,dp[i][j]表示的是从原点到i,j位置的最短路径和。我们首先计算第一行和第一列,直接累加即可,那么对于其他位置,要么是从它左边的位置达到,要么是从上边的位置达到,我们取左边和上边的较小值,然后加上当前的路径值,就是达到当前点的最短路径。然后从左到右,从上到下依次计算即可。

m[i][j] = min(m[i-1][j],m[i][j-1]) + p[i][j]

代码不再详细写,与背包问题类似的矩阵求解。

4 事例四

给定两个字符串str1和str2,返回两个字符串的最长公共子序列,例如:str1="1A2C3D4B56",str2="B1D23CA45B6A","123456"和"12C4B6"都是最长公共子序列,返回哪一个都行。

分析:本题是非常经典的动态规划问题,假设str1的长度为M,str2的长度为N,则生成M*N的二维数组dp,dp[i][j]的含义是str1[0..i]与str2[0..j]的最长公共子序列的长度。

dp值的求法如下:

dp[i][j]的值必然和dp[i-1][j],dp[i][j-1],dp[i-1][j-1]相关,结合下面的代码来看,我们实际上是从第1行和第1列开始计算的,而把第0行和第0列都初始化为0,这是为了后面的取最大值在代码实现上的方便,dp[i][j]取三者之间的最大值。

# 此例中有多个相同长度的公共子串,但只能获取第一个子串
def find_lcsubstr(s1, s2): # 下面4行不要直接写在循环中,减少计算s1_len = len(s1) + 1                    #为方便后续计算,多了1行1列 s2_len = len(s2) + 1s3_len = len(s1)s4_len = len(s2)m = [[0 for j in range(s2_len)] for i in range(s1_len)] #生成0矩阵maxNum = 0                               #初始最长匹配长度p = 0                                     #匹配的起始位置for i in range(s3_len):for j in range(s4_len):if s1[i] == s2[j]:                #相同则累加m[i + 1][j + 1] = m[i][j] + 1 #给相同字符赋值,值为左上角值加1if m[i + 1][j + 1] > maxNum:maxNum = m[i + 1][j + 1]  #获取最大匹配长度p = i + 1                  #记录最大匹配长度的终止位置print(m)return s1[p - maxNum : p], maxNum         #返回最长子串及其长度
print(find_lcsubstr(str_a, str_b))

5 事例五

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解题思路:这个用动态规划感觉就很好啦

终止条件:第N个丑数

状态:s[i] 第i个丑数

状态转移方程: s[i] = min(s[t1]*2 , s[t2]*3, s[t3]*5)。这个t1,t2,t3<i-1

这3个t怎么确定呢?从i-1往后倒腾,满足s[t1-1]*2小于等于s[i-1]但s[t1]*2大于s[i-1]

确定了之后敲代码把~~(代码还有很多可以优化的,但觉得这个可读性比较强,贴上自己的代码)

class Solution:def GetUglyNumber_Solution(self, index):if index<1:return 0if index==1:return 1s = []s.append(1)t1 = 0t2 = 0t3 = 0for i in range(1,index):            for j in range(i):if s[j]*2 > s[i-1]:t1 = jbreakfor j in range(i):if s[j]*3 > s[i-1]:t2 = jbreakfor j in range(i):if s[j]*5 > s[i-1]:t3 = j   breaks.append(min(s[t1]*2,s[t2]*3,s[t3]*5))return s[-1]

6 事例六

找零钱问题,已经零钱面额为1、3、4,求找零n所用零钱数最少的方案

解答过程:对于找零n的最少零钱数f(n),它和f(n-1),f(n-3),f(n-4)有关,即它等于这3者中最小的值加1.

# 找零钱字典,key为面额,value为最少硬币数
change_dict = {}def rec_change(M, coins):change_dict[0] = 0s = 0for money in range(1, M+1):num_of_coins = float('inf')#意思是要求50的最少找零数,在46,47,49的最少找零数中找到最小的即可for coin in coins:if money >= coin:# 记录每次所用的硬币数量if change_dict[money-coin]+1 < num_of_coins:num_of_coins = change_dict[money-coin]+1s = coin #记录每次找零的面额change_dict[money] = num_of_coinsreturn change_dict[M],s# 求出具体的找零方式
# 用path变量记录每次找零的面额
def method(M, coins):print('Total denomination is %d.'%M)nums, path = rec_change(M, coins)#path为最少硬币数方案中的一个面额值print('The smallest number of coins is %d.'%nums)print('%s'%path, end='')while M-path > 0:M -= pathnums, path = rec_change(M, coins)print(' -> %s'%path, end='')print()coins = (1, 3, 4)
method(50, coins)

7 事例七

钢条切割,已经各长度的钢条和对应的收益,问长度为n的钢条怎么切割收益最大。

要求长度为n的钢条切割最大收益,则在n-1最大收益+长度1的收益,n-2最大收益+长度2最大收益……中取最大者。那么依次求长度1~n的钢条最大收益即可。

# 钢条长度与对应的收益
length = (1, 2, 3, 4,5, 6, 7, 8, 9, 10)
profit = (1, 5, 8, 9,10, 17, 17, 20, 24, 30)# 参数:profit: 收益列表, n: 钢条总长度
def bottom_up_cut_rod(profit, n):r = [0] # 收益列表s = [0]*(n+1) # 切割方案列表for j in range(1, n+1):q = float('-inf')#每次循环求出长度为j的钢条切割最大收益r[j],s[j]则保存切割方案中最长的那一段长度for i in range(1, j+1):if max(q, profit[length.index(i)]+r[j-i]) == profit[length.index(i)]+r[j-i]:#元组index从1开始s[j] = i#如果切割方案为1和2,那么2会覆盖1,即保存最长的一段q = max(q, profit[length.index(i)]+r[j-i])r.append(q)#r[n]保存长度为n钢条最大切割收益return r[n], s[n]# 切割方案
def rod_cut_method(profit, n):how = []while n != 0:t,s = bottom_up_cut_rod(profit, n)how.append(s)n -= sreturn how
#输出长度1~10钢条最大收益和最佳切割方案
for i in range(1, 11):t1 = time.time()money,s = bottom_up_cut_rod(profit, i)how =  rod_cut_method(profit, i)t2 = time.time()print('profit of %d is %d. Cost time is %ss.'%(i, money, t2-t1))print('Cut rod method:%s\n'%how)

8 事例八

水杯摔碎问题,有n个水杯和k层楼,求最少测试几次可以确定水杯刚好在哪一层楼摔碎。

解答过程:假设从x层楼开始扔为f(n,x),如果水杯碎了水杯数量-1需要探测的楼层为x-1层,则为f(n-1,x-1),如果没碎水杯还是n个需要探测k-x层,则为f(n,k-x)

import numpy as np#n个水杯k层楼,最少需要几次测试确定水杯在几层楼刚好摔破
def solvepuzzle(n, k):numdrops = np.array([[0]*(k+1)]*(n+1))for i in range(k+1):numdrops[1, i] = i#只有一个水杯,最坏情况是跟楼层数一样for i in range(2, n+1):#2到n个水杯for j in range(1, k+1):#楼层1到kminimum = float('inf')#每次循环得出一种(i,j)下的最少次数for x in range(1, j+1):minimum = min(minimum, (1+max(numdrops[i, j-x], numdrops[i-1, x-1])))numdrops[i, j] = minimumprint(numdrops)return numdrops[n,k]t = solvepuzzle(3, 10)
print(t)

9 事例九

给定n个水杯和d次尝试机会,求最多能探测多少楼层。

解答过程:f(d,n)=f(d-1,n)+f(d-1,n-1),令g(d,n)=f(d,n+1)-f(d,n)=g(d-1,n)+g(d-1,n-1),这跟二项式C(n,k)=C(n-1,k)+C(n-1,k-1)相似,故g(d,n)=C(d,n)-->f(d,n)=求和(C(d,i)) i从1到n-1,i>=d时C(d,i)=0


#n个水杯d次尝试机会,最多探测多少层楼?
#f(d,n)=求和i=1~n-1{C(d,i)} 对所有d>=1 and i<d
def assist(d,i):#C(d,i)sum=1sum2=1for k in range(d-i+1,d+1):sum*=kfor j in range(1,i+1):sum2*=jsum=sum/sum2return sumdef f(d,n):if d<1 or n<1:return 0elif d==1:return 1sum=0for i in range(1,n+1):if i<d:sum+=assist(d,i)return int(sum)
print(f(3,3))
#这里可以逆向求解n个水杯k层楼至少需要多少次测试
def reverseFun(n,k):d=1while f(d,n)<k:d+=1print(d)return d
reverseFun(3,10)

详解动态规划算法(Python实现动态规划算法典型例题)相关推荐

  1. 【机器学习】【隐马尔可夫模型-3】后向算法:算法详解+示例讲解+Python实现

    0.前排提示 csdn有些数学公式编辑不出来,所以本博用容易书写的表达式来表示专业数学公式,如: (1)  在本博客中用α<T>(i)来表示 (2)在本博客中用[i=1, N]∑来表示 注 ...

  2. java斐波那契查找_详解Java Fibonacci Search斐波那契搜索算法代码实现

    一, 斐波那契搜索算法简述 斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术. 斐波那契搜索采用分而治之的方法,其中我们按照斐波那契数列对元素进行不均等 ...

  3. 详解利用基于gensim的TF-IDF算法实现基于文本相似度的推荐算法

    详解利用基于gensim的TF-IDF算法实现基于文本相似度的推荐算法 TF-IDF的基本原理 算法思想 计算公式 相似度计算原理 微型图书推荐案例 案例背景 开发工具 数据预处理 TF-IDF模型建 ...

  4. 国密算法Go语言实现(详解)(九) ——SM2(椭圆曲线公钥密码算法)

    国密算法Go语言实现(详解)(九) --SM2(椭圆曲线公钥密码算法) 原创代码:https://github.com/ZZMarquis/gm 引用时,请导入原创代码库.本文仅以注释方式详解代码逻辑 ...

  5. 国密算法Go语言实现(详解)(十) ——SM2(椭圆曲线公钥密码算法)

    国密算法Go语言实现(详解)(十) --SM2(椭圆曲线公钥密码算法) 原创代码:https://github.com/ZZMarquis/gm 引用时,请导入原创代码库.本文仅以注释方式详解代码逻辑 ...

  6. Paxos算法细节详解(一)--通过现实世界描述算法

    Paxos算法细节详解(一)--通过现实世界描述算法 Paxos分析 最近研究paxos算法,看了许多相关的文章,概念还是很模糊,觉得还是没有掌握paxos算法的精髓,所以花了3天时间分析了libpa ...

  7. python英语字典程序修改_详解如何修改python中字典的键和值

    我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...

  8. python java混合编程_详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...

  9. centos7安装python3_详解Centos7升级python 2.7至Python 3.7

    详解Centos7升级python 2.7至Python 3.7 龙行 个人随笔 2019-6-6 3451 0评论 centos7版本默认安装的是python2.7,对于强迫症的我来说,忍受不了啊. ...

  10. python字典修改键所对应值_详解如何修改python中字典的键和值

    我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...

最新文章

  1. 深度探索C++ 对象模型(7)-Data member的布局(多重继承)
  2. 电脑端二维码识别工具_电脑端自签工具更新,多功能软件一键签名
  3. java合并sheet行_java poi Excel循环合并行
  4. 【英语学习】【Level 07】U03 Amazing wonders L6 My homeland, my pride
  5. python处理时间的标准数据_python数据分析数据标准化及离散化详解
  6. 数字水印技术的基本概念和现状
  7. 通达OA2019版本全功能
  8. linux病毒通过U盘传播,一个通过U盘传播的病毒详细分析,近期抓的但是是09年的病毒...
  9. 单片机音频节奏灯_基于单片机的音乐节奏彩灯控制系统设计
  10. 运用计算机计算电力系统潮流,潮流电力系统论文,关于电力系统潮流计算现状展望相关参考文献资料-免费论文范文...
  11. 华为 A800-9000 服务器 离线安装MindX DL
  12. 不完全免疫算法简介HEIA--AIS学习笔记2
  13. catia螺纹孔在二维图上不显示_catia里面螺纹怎么显示不出来呢??哪儿能设置...
  14. 利用visio 画思维导图
  15. VUE的插槽(slot和slot-scope)
  16. 分区放映,实现互联网电影院新增量
  17. 诺贝尔奖项陆续公布 中国相关研究已居世界高端
  18. 快手上用计算机算出一段话,快手一边说话一边出字是怎么弄的 可以这样做
  19. 什么是文件共享软件?文件传输软件如何共享?
  20. JSPatch 使用Demo

热门文章

  1. 【读书推荐】中国是部金融史
  2. Win10使用Lenovo Vantage更新驱动后蓝屏,提示system_service_exception ETD.sys如何解决
  3. 读《薛兆丰经济学讲义》后感
  4. 数据分析、数据挖掘、机器学习实习面经总结
  5. JavaScript语法 ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性汇总
  6. Java图片相似度,图像识别
  7. 再理解:零空间、行空间、列空间、左零空间、基础解系、极大线性无关组、齐次解、非齐次解之间的关系
  8. 《c语言程序设计》网课答案,C语言程序设计基础知到网课答案
  9. Ubuntu安装配置谷歌拼音输入法
  10. 索骥馆-编程语言之《程序语言的奥妙:算法解读(四色全彩)》扫描版[PDF]