一、斐波那契数列(递归VS动态规划)

1、斐波那契数列——递归实现(python语言)——自顶向下

递归调用是非常耗费内存的,程序虽然简洁可是算法复杂度为O(2^n),当n很大时,程序运行很慢,甚至内存爆满。

 def fib(n):
#终止条件,也就是递归出口
if n == 0 or n == 1:
return 1
else:
#递归条件
return (fib(n-1) + fib(n - 2))

2、斐波那契数列——动态规划实现(python语言)——自底向上

动态规划——将需要重复计算的问题保存起来,不需要下次重新计算。对于斐波那契数列,算法复杂度为O(n)。

 def dp_fib(n):
#初始化一个数组,用于存储记录计算的结果。
res = [None] * (n + 1)
#前两项设置为1。
res[0] = res[1] = 1
#自底向上,将计算结果存入数组内。
for i in range(2, (n + 1)):
res[i] = res[i-1] + res[i-2]
return res[n]

3、方法概要

  (1)构造一个公式,它表示一个问题的解是与它的子问题的解相关的公式:

     

  (2)为这些子问题做索引,以便于它们能够在表中更好的存储与检索(用数组存储)。

  (3)以自底向上的方法来填写这个表格;首先填写最小的子问题的解。

  (4)这就保证了当我们解决一个特殊的子问题时,可以利用比它更小的所有可利用的子问题的解。

总之,因为在上世纪40年代(计算机普及很少时),这些规划设计是与“列表”方法相关的,因此被称为动态规划——Dynamic Programing。

二、动态规划算法——思想简介

1、DP算法思想

  (1)将待求解的问题分解称若干个子问题,并存储子问题的解而避免计算重复的子问题,并由子问题的解得到原问题的解。

   (2)动态规划算法通常用于求解具有某种最有性质的问题。

   (3)动态规划算法的基本要素:最优子结构性质和重叠子问题。

      最优子结构性质:问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次的决策产生)的最优决策。

      重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些问题被反复计算多次。对每个子问题只解一次,然后将其解保存起来,

            以后再遇到同样的问题时就可以直接引用,不必重新求解。

2、DP算法——解决问题的基本特征

  (1)动态规划一般求解最值(最优、最大、最小、最长)问题;

   (2)动态规划解决 的问题一般是离散的,可以分解的(划分阶段的)。

   (3)动态规划结局的问题必须包含最优子结构,即可以有(n-1)的最优推导出n的最优。

3、DP算法——解决问题的基本步骤

  动态规划算法的四个步骤:

    (1)刻画最优解的结构特性。(一维、二维、三维数组);

    (2)递归的定义最优解。(状态转移方程)

    (3)以自底向上的方法来计算最优解。

    (4)从计算得到的解来构造一个最优解。

 4、求解例子——求阶乘 n!

 #递归实现求阶乘
def multiply(n):
if n == 0 or n == 1:
return 1
return n * multiply(n -1) #动态规划实现求阶乘
def dp_multiply(n):
temp = [None] * (n + 1)
temp[0] = 1
temp[1] = 1
for i in range(2, n + 1):
temp[i] = i * temp[i - 1]
return temp[n]

 三、动态规划——常见例题

1、求解最长不降子序列

  (1)方法一:普通方法,算法复杂度为O(n^2)。

      假设原始的数列为数组 a

      分析:

        刻画结构特性:用F[ i ] 表示前 i 项最长不下降子序列的长度;

        状态转移方程:如果a [ i ] >=a [ j ],  F[i] = max(F[i], F[j] + 1)  其中,0 <= j < i

        数据存储:自底向上求解最小子结构最优解存入数组

其中,pre[ i ]表示以元素a [ i ] 为结尾的最长不降序列的前一个元素索引(也就是以a[i]结尾的最长不降序列的倒数第二个元素)。存储这个值是为了方便输出最长的不降序列。

 def Longest_Increaseing(a):
F = [1] * len(a)
pre = [0] * len(a)
for i in range(1, len(a)):
for j in range(i):
if a[i] >= a[j]:
F[i] = max(F[i], F[j] + 1)
pre[i] = j
return F, pre
a = [5,2,8,6,3,6,9,7]
F, pre = Longest_Increaseing(a) #这里只是能获得两个数组,其中F[i]的最大值就是最长不降序列的长度。

接下来,输出最长的不降序列的元素值,请看下面的代码:

 def Longest_Increaseing(a):
F = [1] * len(a)
pre = [0] * len(a)
for i in range(1, len(a)):
for j in range(i):
if a[i] >= a[j]:
F[i] = max(F[i], F[j] + 1)
pre[i] = j
return F, pre
a = [5,2,8,6,3,6,9,7]
F, pre = Longest_Increaseing(a) #最长序列的索引
k = F.index(max(F))
#输出序列的列表
result = [None] * F[k]
flag = True
Len = F[k]
while flag:
result[Len - 1] = a[k]
k = pre[k]
if k == 0:
flag = False
Len -= 1
print(result) #输出结果:[2, 3, 6, 9]

  (2)方法二:时间复杂度为O(n * log(n))

    参考博文:最长不下降子序列 NlogN && 输出序列       https://www.cnblogs.com/milky-w/p/8431333.html

2、求解最长的公共子序列

求解最长公共子序列代码如下(python语言):

 import numpy as np
def LCS(str1, str2):
#获取两个序列的长度
m = len(str1)
n = len(str2)
#生成一个存储计算子问题的二位矩阵,并将元素初始化为0。
#这个矩阵的尺寸比两个序列的尺寸分别大1个单位。
#对于这个矩阵,第一行和第一列元素值必然为0。
#C[i][j]的含义是:Xi = (x1, x2, x3,..., xi)和Yj = (y1, y2, x3,..., yj)的最长公共子序列
C = np.zeros((m+1, n+1), dtype=int)
b = np.zeros((m+1, n+1), dtype=int) for i in range(1, m+1):
for j in range(1, n+1):
#请注意这里为什么是i-1和j-1,因为其实C[1][1]表示的是
# 两个序列的首个元素的最长公共子序列,对应的是str1[0]和str2[0]
if str1[i-1] == str2[j-1]:
C[i][j] = C[i-1][j-1] + 1
b[i][j] = 1 #表示对角线方向
else:
if C[i][j-1] <= C[i-1][j]:
b[i][j] = 2 #表示朝上方向
else:
b[i][j] = 3 #表示朝左方向
C[i][j] = max(C[i][j-1], C[i-1][j])
return C, b test1 = ['b', 'd','c', 'a', 'b', 'a']
test2 = ["a","b","c","b","d","a","b"]
a, b = LCS(test2, test1) print(a)
#矩阵a存储的是公共子序列的长度,最大值就是最大公共子序列的长度

[[0 0 0 0 0 0 0]
[0 0 0 0 1 1 1]
[0 1 1 1 1 2 2]
[0 1 1 2 2 2 2]
[0 1 1 2 2 3 3]
[0 1 2 2 2 3 3]
[0 1 2 2 3 3 4]
[0 1 2 2 3 4 4]]

 print(b)
#这里: 1表示对角线方向、2表示朝上、3表示朝左,主要是为了求具体的子序列用的。

[[0 0 0 0 0 0 0]
[0 2 2 2 1 3 1]
[0 1 3 3 2 1 3]
[0 2 2 1 3 2 2]
[0 1 2 2 2 1 3]
[0 2 1 2 2 2 2]
[0 2 2 2 1 2 1]
[0 1 2 2 2 1 2]]

接下来是输出最长公共子序列:

 import numpy as np
def LCS(str1, str2):
#获取两个序列的长度
m = len(str1)
n = len(str2)
#生成一个存储计算子问题的二位矩阵,并将元素初始化为0。
#这个矩阵的尺寸比两个序列的尺寸分别大1个单位。
#对于这个矩阵,第一行和第一列元素值必然为0。
#C[i][j]的含义是:Xi = (x1, x2, x3,..., xi)和Yj = (y1, y2, x3,..., yj)的最长公共子序列
C = np.zeros((m+1, n+1), dtype=int)
b = np.zeros((m+1, n+1), dtype=int) for i in range(1, m+1):
for j in range(1, n+1):
#请注意这里为什么是i-1和j-1,因为其实C[1][1]表示的是
# 两个序列的首个元素的最长公共子序列,对应的是str1[0]和str2[0]
if str1[i-1] == str2[j-1]:
C[i][j] = C[i-1][j-1] + 1
b[i][j] = 1 #表示对角线方向
else:
if C[i][j-1] <= C[i-1][j]:
b[i][j] = 2 #表示朝上方向
else:
b[i][j] = 3 #表示朝左方向
C[i][j] = max(C[i][j-1], C[i-1][j])
return C, b def Print_Lcs(b, X, i , j):
if i == 0 or j == 0:
return
if b[i][j] == 1:
Print_Lcs(b, X, i-1, j-1)
print(X[i-1]) #为什么是i-1,因为b矩阵的行比X的行长一个单位,而且只输出相等的值,表示公共元素。
elif b[i][j] == 2:
Print_Lcs(b, X, i-1, j)
else:
Print_Lcs(b, X, i, j-1) if __name__ == '__main__':
test1 = ['b', 'd','c', 'a', 'b', 'a']
test2 = ["a","b","c","b","d","a","b"]
a, b = LCS(test2, test1)
Print_Lcs(b, test2, 7, 6) #输出的结果是: b、c、b、a 。(请注意这里结果不唯一,因为最长子序列长度为4, 存在三个序列长度为4的子序列)

动态规划——DP算法(Dynamic Programing)的更多相关文章

  1. c++动态规划dp算法题

    问题1:找硬币,换钱的方法 输入: penny数组代表所有货币的面值,正数不重复 aim小于等于1000,代表要找的钱 输出:换钱的方法总数 解法1:经典dp,空间复杂度O(n*aim) class ...

  2. Siimple DP (Dynamic Programing)

    HDU 2084:https://vjudge.net/problem/HDU-2084 Problem Describe : When it comes to the DP algorithm, a ...

  3. 算法-动态规划DP小记

    算法-动态规划DP小记 动态规划算法是一种比较灵活的算法,针对具体的问题要具体分析,其宗旨就是要找出要解决问题的状态,然后逆向转化为求解子问题,最终回到已知的初始态,然后再顺序累计各个子问题的解从而得 ...

  4. 0-1背包的动态规划算法,部分背包的贪心算法和DP算法------算法导论

    一.问题描述 0-1背包问题,部分背包问题.分别实现0-1背包的DP算法,部分背包的贪心算法和DP算法. 二.算法原理 (1)0-1背包的DP算法 0-1背包问题:有n件物品和一个容量为W的背包.第i ...

  5. 最大子段和的DP算法设计及其效率测试

    表情包形象取自番剧<猫咪日常> 那我也整一个 曾几何时,笔者是个对算法这个概念漠不关心的人,由衷地感觉它就是一种和奥数一样华而不实的存在,即便不使用任何算法的思想我一样能写出能跑的程序 直 ...

  6. 动态规划dp

    一.概念:动态规划dp:是一种分阶段求解决策问题的数学思想. 总结起来就一句话:大事化小,小事化了 二.例子 1.走台阶问题 F(10):10级台阶的走法数量 所以:F(10)=F(9)+F(8) F ...

  7. 华为笔试——C++平安果dp算法

    题目:平安果 题目介绍:给出一个m*n的格子,每个格子里有一定数量的平安果,现在要求从左上角顶点(1,1)出发,每次走一格并拿走那一格的所有平安果,且只能向下或向右前进,最终到达右下角顶点(m,n), ...

  8. C++数字三角形问题与dp算法

    题目:数字三角形 题目介绍:如图所示的数字三角形,要求从最上方顶点开始一步一步下到最底层,每一步必须下一层,求出所经过的数字的最大和. 输入:第一行值n,代表n行数值:后面的n行数据代表每一行的数字. ...

  9. dp算法之硬币找零问题

    题目:硬币找零 题目介绍:现在有面值1.3.5元三种硬币无限个,问组成n元的硬币的最小数目? 分析:现在假设n=10,画出状态分布图: 硬币编号 硬币面值p 1 1 2 3 3 5 编号i/n总数j ...

随机推荐

  1. CSIC_716_20191126【面向对象编程--继承】

    继承 什么是继承:继承是新建类的一种方式,通过此方式生成的类称为子类.或者 派生类,被继承的类称为父类.基类或超类.在python中,一个子类可以继承多个父类. 继承的作用:减少代码的冗余,提高开发效 ...

  2. scala中的闭包简单使用

    object Closure { /** * scala中的闭包 * 函数在变量不处于其有效作用域内,还能够对变量进行访问 * * @param args */ def main(args: Arra ...

  3. requestAnimationFrame动画封装

    function Animator(duration, progress) { this.duration = duration; this.progress = progress; this.nex ...

  4. (转)谈谈Android中的Rect类——奇葩的思维

    最近在工作中遇到了一些问题,总结下来就是Android中Rect这个类造成的.不得不说,不知道Android SDK的开发人员是怎么想的, 这个类设计的太奇葩了.首先介绍一下Rect类:Rect类主要 ...

  5. 牛客多校第五场 G subsequence 1 最长公共子序列/组合数

    题意: 给定两个由数字组成的序列s,t,找出s所有数值大于t的子序列.注意不是字典序大. 题解: 首先特判s比t短或一样长的情况. 当s比t长时,直接用组合数计算s不以0开头的,长度大于t的所有子序列 ...

  6. POJ-2253-Frogger-/Floyd-Warshall/

    Freddy Frog is sitting on a stone in the middle of a lake. Suddenly he notices Fiona Frog who is sit ...

  7. Oracle一条数据多表连插

    insert all into T_TRAIN_MARSHALLING <trim prefix="(GKEY," suffix=")" suffixOv ...

  8. PAT甲级——A1121 Damn Single【25】

    "Damn Single (单身狗)" is the Chinese nickname for someone who is being single. You are suppo ...

  9. python基础语法(运算符及优先级)

    python基础语法(运算符及优先级) python语言支持的运算符类型 算数运算符 假设变量a为10,变量b为21 算数符 描述 实例 + 加-两个对象相加 a+b结果31 - 减-得到一个负数或者 ...

  10. iOS开发系列-iOS签名机制

    概述 想要了解iOS的签名机制需要有一定密码学有一定的了解.下面依次介绍的数据的加密解密.单向散列函数.数字签名.证书.iOS签名机制. 数据加密解密 在网络通信中想要防止数据被攻击者拦截,我们通常对 ...