1.1斐波那契数列问题
方法1:递归解法
def fibnacci(n):
if n==1 or n==2:
return 1
else:
return fibnacci(n-1)+fibnacci(n-2)
问题:子问题重复计算
# 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(2)=f(1)
方法2:非递归解法,可以理解为动态规划的思想
最优子结构:递推式+重复子问题
子问题存起来
def fibnacci_no_recursion(n):
f=[0,1,1]
if n >2:
for i in range(n-2):
num=f[-1]+f[-2]
f.append(num)
return f[n]
print(fibnacci(100))
1.2钢条切割问题
问题:现有一段长度为的钢条和上面的价格表,求切割钢条
方案,使得总收益最大。
最优子结构:
-
可以将求解规模为的原问题,划分为规模更小的子问题:完成一次切割
后,可以将产生的两段钢条看成两个独立的钢条切个问题。 -
组合两个子问题的最优解,并在所有可能的两段切割方案中选取组合收
益最大的,构成原问题的最优解。 -
钢条切割满足最优子结构:问题的最优解由相关子问题的最优解组合而
成,这些子问题可以独立求解。
-
递归:自顶向下实现
# p=[0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,33,36,39,40]
p=[0,1,5,8,9,10,17,17,20,24,30]
#方法1:
def cut_rod_recurision_1(p,n):
if.n==0:
return 0
else:
res= p[n]
for i in range(1,n):
res max(res,cut_rod_recurision(p,i)+cut_rod_recurision(p,n-i)) #重复计算
return res
# 递归2
def cut_rod_recurision_2(p,n):
if.n==0:
return 0
else:
res=0
for i in range(1,n+1):
res max(res,p[i]+cut_rod_recurision_2(p,n-i))
return res
- 动态规划:自底向上实现
- 递归算法由于重复求解相同子问题,效率极低
- 动态规划的思想:
- 每个子问题只求解一次,保存求解结果
- 之后需要此问题时,只需查找保存的结果
def cut_rod_dp(p,n):
r=[0]
for i in range(1,n+1):
res 0
for j in range(1,i+1):
res max(res,p[j]+r[i j])
r.append(res)
return r[n]
- 重构解
def cut_rod_extend(p,n):
r.=[0]
s=[0]
for i in range(1,n+1):
res_r=O#价格的最大值
res_s.=O#价格最大值对应方案的左边不切割部分的长度
for j in range(1,i 1):
if p[j]+r[i j]>res_r:
res-r=p[j]+.r[i.-j门
res_s=j
r.append(res_r)
s.append(res_s)
return r[n],s
def cut_rod_solution(p,n)
r,s=cut_rod_extend(p,n)
ans=[]
while n>0:
ans.append(s[n])
n-=s[n]
总结
- 动态规划问题关键特征
- 什么问题可以使用动态规划方法?
- 最优子结构
- 原问题的最优解中涉及多少个子问题
- 在确定最优解使用哪些子问题时,需要考虑多少种选择
- 重叠子问题
1.3最长公共子序列
一个序列的子序列是在该序列中删去若干元素后得到的序列。
例:“ABCD”和“BDF”都是“ABCDEFG”的子序列
最长公共子序列(LCS)问题:给定两个序列X和Y,求X和Y长度最大的公共子
序列。
D例:X="ABBCBDE"Y="DBBCDB"LCS(X,Y)=“BBCD”
应用场景:字符串相似度比对
思考:暴力穷举法的时间复杂度是多少?
2的m+n次方
思考:最长公共子序列是否具有最优子结构性质?有