DP 动态规划初识
前面的 HMM 中参数求解, 都会用到动态规划, 全是各种概率公式, 是有一些抽象, 今天决定举个一波简单的栗子, 帮助理解DP 把一个大问题,不断划分为更小的子问题来求解的这种方式, 就是动态规划. 这是最为直观和通俗的理解.
DP vs 递归
我之前也是经常把 DP 和递归弄混淆. 递归, 其实就是, 函数调用自身. 在某种程度上来说, 递归和DP有其相似性. 我的理解是, DP 降低了 递归 的时间复杂度, 具体说, DP解决了递归的重复子问题计算(overlap) 问题. 为了说明其问题, 从 B站找了几段视频来帮助理解.
case: 斐波那契数列
已知: 1, 1, 2, 3, 5, 8, 13, 21, 34 ......
求第 n 个数的值.
其实这就是一个非常典型的, 用递归来表述的一类问题, 递归专注两个因素: 退出条件, 递推关系
"""
主题:
动态规划 之 斐波那契数列
问题特点:
类似递归, 有很多的重复子问题 overlap
描述:
f(1) = 1
f(2) = 1
f(3) = f(2) + f(3)
f(4) = f(3) + f(2)
...
f(n) = f(n-1) + f(n-1)
"""
def rec_opt(n):
"""递归-求解斐波那契数列"""
if n <= 2:
return 1
return rec_opt(n - 1) + rec_opt(n - 2)
非常好理解, 但这种写法会带来什么问题呢? 就是时间复杂度高, 是 \(O(2^n)\), 比如要计算 f(5):
f(5) = f(4) + f(3) 然后分为两叉
左边: f(4) = f(3) + f(2)
f(3) = f(2) + f(1)
右边: f(3) = f(2) + f(1)
咋眼一看, 兄弟, 有没有发现, 左边在计算 f(4) 的时候, 计算了 f(3), f(2), f(1); 而右边在计算 f(3) 的时候, 又在重复计算 f(2), f(1), 随着规模越大, 重复计算的量会呈指数级增长, 这个就叫做 overlap. 这就是递归, 在这个时候, 会极大第增加时间复杂度.
其实我们如何这样想, 既然在计算 f(3) 的时候, 会用到, f(1), f(2); f(4) 的时候, 会用到 f(3) + f(2) ... 那, 我们为什么不会想到, 把这些计算好的历史数据给存起来, 直接用呢?
即, 可以用一个 list 将历史数据给存起来, 要用的时候, 直接索引就ok了, 复杂度是: \(O(n)\) 这样利用历史数据来推测后面的方法, 就是动态规划.
- 退出条件
- 初始值
- 状态转移方程
def dp_opt(n):
"""DP 求解斐波那契数列"""
# 用来存历史数据的列表
lst = [0 for _ in range(n)]
# 退出条件
if n < 2:
return 1
# 初始化数列的第 0,1 项
lst[0], lst[1] = 1, 1
for i in range(2, n):
# 状态转移方程
lst[i] = lst[i - 1] + lst[i - 2]
return lst[n - 1]
正好来测试一波时间对比
def calc_func_time(func, n):
print(f"test 规模为{n}的函数{func}运行时间为:")
start_time = time.time()
func(n)
end_time = time.time()
print("time used", end_time - start_time, "s.")
if __name__ == '__main__':
# 递归就是不容易使用装饰器写计时器来哦
n = 40 # 规模
calc_func_time(dp_opt, n)
calc_func_time(rec_opt, n)
C:\Python\Python36\python.exe E:/Pytest/base_case/DP-斐波那契数列.py
test 规模为40的函数<function dp_opt at 0x00000271927B7840>运行时间为:
time used 0.0 s.
test 规模为40的函数<function rec_opt at 0x0000027190902E18>运行时间为:
time used 39.237488985061646 s.
可以看到, 递归的复杂度真的太可怕了, 而DP非常淡定,
当规模超过1000, 递归, 我的电脑都不动了...而可以测试一波 DP, 更大的.
if __name__ == '__main__':
# 递归就是不容易使用装饰器写计时器来哦
n = 10000 # 1万规模
calc_func_time(dp_opt, n)
test 规模为100000的函数<function dp_opt at 0x0000026675A67840>运行时间为:
time used 0.5376913547515869 s.
DP 果然牛逼! 毕竟复杂度为 \(O(n)\) 嘛
DP 入门级案例
举个简单的栗子来熟悉求解流程即可, 稍微复杂的, 我自己个还在研究, 不敢妄言.....
全篇想说明一个核心技巧: 选 or 不选.
case1: 爬 n阶 台阶
事实是这样的: 小陈同学有一双令人无比羡慕的大长腿, 现在呢, 要去爬一个有 n 阶的台阶, 每次要么向上走1步, 要么向上走3步, 请问, 小陈同学从底部爬到台阶顶端, 一共有多要种可能的方案?
分析:
首先定义一个函数 f(n) 表示爬到第 n 阶级时有 最多的方案数 则
f(1) = 1 最多1种方案, 向上1步
f(2) = 1 最多1中方案, 走1步, 再走1步
f(3) = 2 最多2种方案, 走1步重复3次; 或直接走3步
f(4) = f(1) + f(3) 最多3种方案, 1步重复4次; 先1步,再3步; 先3步, 再1步
...
其实就是 f(n), 在要到达 n 前有2个选择: 要么从选择 f(n-1); 要么选择f(n-3) , 两种情况之和, 就是总方案数
f(n) = f(n-1) + f(n-3)
def dp_craw(n):
# 1. 用list来存储历史数据,可理解为最优方案的函数值
opt = [0 for _ in range(n+1)]
# 2. 写退出条件
if n <= 2:
return 1
elif n == 3:
return 2
# 3. 初始化历史数据
opt[0] = 1
opt[1] = 1
opt[2] = 2
# 4. 状态转移方程
for i in range(2, n):
opt[i] = opt[i-1] + opt[i - 3]
return opt[n-1]
if __name__ == '__main__':
print(dp_craw(50))
C:Python36\python.exe E:/Pytest/base_case/DP爬台阶.py
83316385
当然这这样写还是有重复项的, 复杂度并非 \(O(n)\) 嗯, 不想改了, 主要是阐明这种思路就好.
case2: 给定一堆数字求最大值 (带约束)
"""
描述:
arr = [1, 2, 4, 1, 7, 8, 3]
下标: 0, 1, 2, 3, 4, 5, 6
需求:
目标: 从数组(列表) 中选择任意个元素, 求出其之和最大的值
约束: 相邻元素不能选, 如选了8, 则不能选 7 和 3
思路:
定义一个最优方案函数 opt(n) 表示前n个中最好的选择方案
比如本例, opt(1) = 1, opt(2)=1
tips: 选和不选
抽象: 对于下标i, 有选和不选
if 选择当前值arr[i]:
值 = opt(i-2) + arr[i] 不能选相邻元素, 从前i-2个中选最好方案
else:
值 = opt(i-1) 不选,则中前i-1 个中选择最好方案
最后比较取最大, 即: max( (opt(i-1) + arr[i]), opt(i) )
递归出口:
opt[0] = arr[0] 前下标0个, 即第一位, 只有一种最优方案,就是选第一个值arr[0]
opt[1] = max( arr[0], arr[1]) 前两个,则从1,2中选最大
"""
def rec_opt(arr, i):
"""递归"""
if i == 0:
return arr[0]
elif i == 1:
return max(arr[0], arr[1])
else:
select_i = rec_opt(arr, i - 2) + arr[i]
no_select_i = rec_opt(arr, i - 1)
return max(select_i, no_select_i)
# 递归是产生很多的重叠子问题, overlap 之前演示过与DP的对比,复杂度高很多
def dp_opt(arr):
"""DP实现"""
n = len(arr)
# 用一个list来存储前 i 个最优方案的值, opt 比 lst 要更形象些
opt = [0 for _ in range(n)]
# 初始化前0, 前1的小标下的最优方案值
opt[0] = arr[0]
opt[1] = max(arr[0], arr[1])
# 从第3个元素起, 后面项将前面的项作为其子问题
for i in range(2, n):
# 选择下标为i的值和不选, 比较取最大, 前面的值都有存opt
select_i = opt[i - 2] + arr[i]
no_select_i = opt[i - 1]
opt[i] = max(select_i, no_select_i)
return opt[n - 1]
if __name__ == '__main__':
opt = [1, 2, 4, 1, 7, 8, 3]
opt1 = [4, 1, 1, 9, 1]
print(rec_opt(opt, 6))
print(dp_opt(opt1))
case3: 数字之和为定值
"""
描述:
arr = [3, 34, 4, 12, 5, 12]
S = 9
需求:
从 arr 中选择数字, 使其值和等于定值 S=9, 如果可以返回 True, 否则 False
思路:
跟之前一样的思路: 对于每个元素, 有两种选择,选 or 不选
定义一个subset(arr, i, s)
if 选择arr[i]:
A = subset(arr, i, s-arr[i])
else:
B = subset(arr, i-1, s)
A or B 是True 则True
退出条件:
if sub_set(arr[i], s) 中 s=0 则 return True
if sub_set(arr[0], s) 中 arr[0] != s return False
还约定, arr的每个元素都是正整数.即当 arr[i] > s, 只考虑不选, 即: sub_set(arr[i-1], s)
"""
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:
# arr[i] < s, 有选和不选两种情况
select_i = rec_subset(arr, i - 1, s - arr[i])
no_select_i = rec_subset(arr, i - 1, s)
# 二者其中一个满足条件即可
return select_i or no_select_i
import numpy as np
# 用2维数组来记录,行代表arr[i], 列代表s=1, s=2...
def dp_subset(arr, s):
"""DP求解"""
subset = np.zeros((len(arr), s + 1), dtype=bool)
subset[:, 0] = True
subset[0, :] = False
subset[0, arr[0]] = True
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:
select_i = subset[i - 1, s - arr[i]]
no_select_i = subset[i - 1, s]
subset[i, s] = select_i or no_select_i
row, col = subset.shape
return subset[row - 1, col - 1]
if __name__ == '__main__':
lst = [3, 34, 4, 12, 5, 12]
print(rec_subset(lst, len(lst) - 1, 9))
print(dp_subset(lst, 9))
True
True
入门就到这吧, 后面再研究一波复杂的DP. 感觉还是非常锻炼思维能力的. 嗯, 咋说呢, 我感觉对我有点难, 尤其是多维数组, 图, 树这一块的抽象... 用我同事的话说, 就是我, 数论可以, 但空间不行...这感觉, 老天爷不赏饭呀....
DP 动态规划初识的更多相关文章
- Day 5 笔记 dp动态规划
Day 5 笔记 dp动态规划 一.动态规划的基本思路 就是用一些子状态来算出全局状态. 特点: 无后效性--狗熊掰棒子,所以滚动什么的最好了 可以分解性--每个大的状态可以分解成较小的步骤完成 dp ...
- (转)dp动态规划分类详解
dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间 ...
- 【模板整合计划】DP动态规划
[模板整合计划]DP动态规划 一:[背包] 1.[01背包] 采药 \([P1048]\) #include<algorithm> #include<cstdio> int T ...
- DP动态规划学习笔记——高级篇上
说了要肝的怎么能咕咕咕呢? 不了解DP或者想从基础开始学习DP的请移步上一篇博客:DP动态规划学习笔记 这一篇博客我们将分为上中下三篇(这样就不用咕咕咕了...),上篇是较难一些树形DP,中篇则是数位 ...
- 树形DP——动态规划与数据结构的结合,在树上做DP
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构的第15篇,也是动态规划系列的第4篇. 之前的几篇文章当中一直在聊背包问题,不知道大家有没有觉得有些腻味了.虽然经典的文 ...
- 初识DP动态规划
一.多阶段决策过程的最优化问题 在现实生活中,有类活 动的过程,由于 它的特殊性,可将过程分成若干个互相阶段.在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果.当阶段决策的选取不是任意确 ...
- [原]POJ1141 Brackets Sequence (dp动态规划,递归)
本文出自:http://blog.csdn.net/svitter 原题:http://poj.org/problem?id=1141 题意:输出添加括号最少,并且使其匹配的串. 题解: dp [ i ...
- DP动态规划练习
先来看一下经典的背包问题吧 http://www.cnblogs.com/Kalix/p/7617856.html 01背包问题 https://www.cnblogs.com/Kalix/p/76 ...
- 5. Longest Palindromic Substring(最长回文子串 manacher 算法/ DP动态规划)
Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...
- 摆花 (DP动态规划)
2012_p3 摆花 (flower.cpp/c/pas) 时间限制: 1 Sec 内存限制: 128 MB提交: 17 解决: 10[提交][状态][讨论版][命题人:外部导入] 题目描述 3. ...
随机推荐
- 安川MOTOMAN示教盒触摸不良维修及解决方法
1.安川MOTOMAN示教盒触摸不良或局部不灵. (解决方法:更换触摸面板) 2.安川MOTOMAN示教盒无显示. (解决方法:维修或更换内部主板或液晶屏) 3.安川MOTOMAN示教盒显示不良.竖线 ...
- captura怎样解决FFmpeg解析错误问题
captura怎样解决FFmpeg解析错误问题?captura软件里大家在进行屏幕录制的工作得时候都会用到captura软件,软件得功能可以满足大家的需求,可以轻松的录制屏幕,进行屏幕截屏等,但是又小 ...
- .NET周刊【2月第3期 2025-02-16】
国内文章 我们是如何解决abp身上的几个痛点 https://www.cnblogs.com/jackyfei/p/18709265 张飞洪分享了abp框架在.net社区的使用经验,认为其在模块化.D ...
- 【渗透测试】Vulnhub DarkHole
渗透环境 攻击机: IP: 192.168.216.129(Kali) 靶机: IP:192.168.216.130 靶机下载地址:https://www.vulnhub.com/entr ...
- 【论文随笔】会话推荐系统综述(A Survey on Conversational Recommender Systems)
前言 今天读的论文为一篇于2021年5月发表在<ACM计算机调查>(ACM Computing Surveys)的论文,文章提供了对话式推荐系统(CRS)的全面综述,探讨了CRS的定义.概 ...
- Coordinate Spaces
Coordinate Spaces 本主题包含以下部分: 根空间 用户空间 像素空间 任何VisionPro图像支持一系列坐标空间,以提供一个数值框架来表达特定特征的位置.最有用的空间是根空间,它将点 ...
- UE5 C++ 程序进程退出
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include ...
- Mysql导入数据的时候报错Unknown collation: 'utf8mb4_0900_ai_ci'什么问题?
最近从线上把数据导出来想搭建到本地的时候报了这么一个错? [ERR] 1273 - Unknown collation: 'utf8mb4_0900_ai_ci' 这个错误究竟是什么原因影响的呢? 是 ...
- Java List和Array之间的转换
import java.util.Arrays; import java.util.List; class Test { //Object数组向List的转换 public static List&l ...
- msvcp110.dll丢失修复 按我的方法来,保证修复!
方法很简单,msvcp110.dll丢失,安装Microsoft Visual C++ 2012 Redistributable Package就可以,我把修复程序的链接放下面.链接地址: 链接:ht ...