举例:
递归方法没有非递归方法快,因为递归中,子问题重复计算导致执行效率低
求 f(6)时需要求 f(5),f(4),f(3) ; 等到求 f(7)时,还需要再一次求 f(6), f(5),f(4),f(3)。
# 斐波那契数列
# 递归
def fibnacci(n):
if n==1 or n==2:
return 1
else:
return fibnacci(n-1)+fibnacci(n-2)
print(fibnacci(7))
# 非递归------比较快,因为递归的执行效率低,子问题的重复计算导致的
# 动态规划(DP)的思想 = 最优子结构,即递推式 + 重复子问题的存储(利用循环把需要的子问题存储)
def fibnacci_no_recurision(n):
f = [0,1,1]
if n>2:
for i in range(n-2):
num = f[-1] + f[-2]
f.append(num)
return f[-1] #reutrn f[n]
# 这里用队列更准确
print(fibnacci(7))
问题一:钢条切割问题
一共有 2的n-1次方 种。
需要得到递推式(1):
钢条切割问题——最优子结构
自顶向下递归实现 :
# 递归推导式(1)
p1=[0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,36,39,40,53]
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_1(p,i)+cut_rod_recurision_1(p,n-i))
return res
print(cut_rod_recurision_1(p1,20))
得到递推式(2):
# 递归推导式(2)
p1=[0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,36,39,40,53]
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 # 长度为n的钢铁,所有切割方案中的最优收益
print(cut_rod_recurision_2(p1,20))
动态规划实现: 将递归的部分(重复求解子问题)保存起来。
p1=[0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,36,39,40,53]
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]
print(cut_rod_dp(p1,20))
r[i] 表示最优切割收益;s[i] 表示左边不切割部分的长度
p1=[0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,36,39,40,53]
def cut_rod_extend(p,n):
r=[0] # 最有收益
s=[0] # 最优收益情形中,左边不切割部分的长度
for i in range(1,n+1):
res_r = 0 # 价格最大值
res_s = 0 # 价格最大值对应方案的左边不切割部分的长度
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) # 得到最优收益和方案s
ans = [] # 切割方案
while n > 0:
ans.append(s[n])
n -= s[n]
return ans
r,s = cut_rod_extend(p1,10)
print(s) # 输出方案s[0, 1, 2, 3, 2, 2, 6, 1, 2, 3, 2],看不懂,如何直接输出结果
print(cut_rod_solution(p1,10)) # 给出切割方案
最优化问题;先考虑是否为递归问题,如果是递归问题,那么很大程度上可以用动态规划解。
问题一:最长公共子序列(LCS)
字符串相似度比对——模糊查找 ; 百度搜索一个问题
思考:暴力穷举法的时间复杂度时多少? O( 2^(m+n) )
思考:最长公共子序列是否具有最优子结构性质?
c[i,j] 表示最长公共子序列的长度。 有了递推公式,可以使用递归,也可以用动态规划来实现,构造一个二维列表(m+1,n+1):
- 最长公共子序列长度
- 长度求解完成,但是怎么给出求解子序列——回溯法
# 求最长公共序列长度和箭头矩阵
def lcs(x,y):
m=len(x)
n=len(y)
c=[[0 for _ in range(n+1)] for _ in range(m+1)] # 公共子序列长度矩阵
b=[[0 for _ in range(n+1)] for _ in range(m+1)] # 1:左上方 2:左方 3:上方
for i in range(1,m+1):
for j in range(1,n+1):
if x[i-1] == y[j-1]:
c[i][j] = c[i-1][j-1]+1
b[i][j] = 1 # 表示该位置来自 斜上方
elif c[i-1][j] > c[i][j-1]:
c[i][j] = c[i-1][j]
b[i][j] = 3
else: # c[i-1][j] < c[i][j-1]
c[i][j] = c[i][j-1]
b[i][j] = 2
for _ in c:
print(_)
print('------------')
for _ in b:
print(_)
return c[m][n],b
# 求得最长公共子序列(回溯法)
def trackback(x,y):
res=[] # 保存公共序列的列表
c,b=lcs(x,y)
m=len(x)
n=len(y)
while m>0 and n>0: # 回溯到第一列或第一行停止
if b[m][n] == 1: # 来自 左上方----->匹配
res.append(x[m-1]) # 同一个字母在x中的下标小于矩阵的横轴
m-=1
n-=1
elif b[m][n] ==2: # 来自左边
n-=1
else: #来自上方
m-=1
return ''.join(reversed(res)) # res里字母顺序反
print(trackback('ABCBDAB','BDCABA'))