Fn =Fn-1 + Fn-2
#子问题的重新计算 def fabnacci(n): if n == 1 or n == 2: return 1 else: return fabnacci(n-1)+fabnacci(n-2) #非递归算法:动态规划思想DP def fabnacci_no_rec(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]
动态规划:最优子结构→递归式
某公司出售钢条,出售价格与钢条长度之间的关系如下表:
现有一段长度为n的钢条和上面的价格表,求切割钢条方案,使总收益最大
长度为n的钢条 有 2^(n-1)种切割方案
设长度为n的钢条最优收益为r_n,递推式为
r_n = max(p_n, r_1+r_n-1, r_2+r_n-2, r_3+r_n-3……, r_n-1+r_1)
p_n表示不切割
其他n-1个参数分别表示另外n-1种不同切割方案,对方案 i=1,2…,n-1:
将钢条分为长度为 i 和n-i两段
方案 i 的收益为切割两段的最有收益之和
考察所有的 i ,选择其中收益最大的方案
从钢条的左边切割下长度为 i 的一段,只对右边剩下的一段继续进行切割,左边的不再切割
递推式简化为:r_n = max(p_i + r_n-1) 1 <= i <= n-i
print(cut_rad_rec_1(p,9))
不做切割的方案可以描述为:左边一段长度为n,收益为p_n,剩余一段长度为0,收益为r_0=0
可以将求解规模为n的原问题,划分为规模更小的子问题:完成一次切割后,可以将产生的两段钢条看成两个独立的钢条切割问题。
组合两个子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大的,构成原问题的最优解。
钢条切割问题满足最优子结构:问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解。
方法一:简化前写法
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] def cut_rod_rec_1(p, n): if n== 0: return 0 else: res = p[n] for i in range(1,n): res = max(res, cut_rod_rec_1(p,i) + cut_rod_rec_1(p,n-i)) return res print(cut_rod_rec_1(p,9))
方法二:简化后写法
def cut_rod_rec_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_rec_2(p,n-i)) return res
速度更快
自顶向下递归实现效率差:时间复杂度 O(2^n)
递归算法由于重复求解相同子问题,效率极低
动态规划的思想:
每个子问题只求解一次,保存求解结果
之后需要此问题时,只需查找保存的结果
先算r_1, 存起来,再算r_2……
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]
时间复杂度 O(n^2)
如何输出最优切割方案?
——对于每个子问题,保存切割一次时左边切下的长度
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) ans = [] #存放最终切割方案 while n > 0: ans.append(s[n]) n -= s[n] return r, ans
1、量化子结构
原问题的最优解中涉及多少个子问题
在确定最优解使用哪些子问题时,需要考虑多少种选择
2、重叠子问题
一个序列的子序列是在该序列中删去若干元素后得到的序列
最长公共子序列(LCS)问题:给定两个序列X和Y,求X和Y长度最大的公共子序列。
例如:X=“ABBCBDE” Y="DBBCDB" LCS(X,Y)="BBCD"
应用场景:字符串相似度比对
暴力穷举时间复杂度: 2^(m+n) , m、n为两个序列的长度
LCS是否具有动态规划的最优子结构性质?
最后结果就是 c[m, n]
def lcs(x,y): m = len(x) n = len(y) c = [[0 for _ in range(n + 1)] for _ in range(m + 1)] #m+1 * n+1 初始化都是0 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]: #i j上的字符相等 匹配 c[i][j] = c[i-1][j-1] + 1 b[i][j] = 1 elif c[i-1][j] > c[i][j-1]: #i j上的字符不相等 不匹配 从上方来 c[i][j] = c[i-1][j] b[i][j] = 2 else: #从左方来 c[i][j] = c[i][j-1] b[i][j] = 3 return c[m][n], b def lcs_trackback(x, y): c, b = lcs(x, y) i = len(x) j = len(y) res = [] #存放最长公共子序列 while i > 0 and j > 0: if b[i][j] == 1: #来自左上方 说明该位置字符匹配 res.append(x[i-1]) i -= 1 j -= 1 elif b[i][j] == 2: #来自上方 i -= 1 else: #来自左方 j -= 1 return "".join(reversed(res)) x = "ABCBDAB" y = "BDCABA" c, b = lcs(x,y) print(lcs_trackback(x, y))
<可能有多个最长公共子序列>