《算法导论》:动态规划学习笔记

参考资料:《算法导论》

动态规划方法

动态规划方法用于解决这样一类问题:

  • 问题具有最优子结构性质,即如果这个问题达到了最优解,那么此时构成它的子问题也应当是最优解, 这与贪心算法类似
  • 子问题空间应该足够"小", 即问题的递归算法会反复求解相同的子问题,而不是一直生成新的子问题
  • 可能需要尝试不同的划分方法,将问题分解成不同的子问题,以求得全局最优解

一般求解步骤:

  • 刻画一个最优解的结构特征
  • 递归地定义最优解的值
  • 通常采用自底向上的方法计算最优解的值
  • 构造最优解

问题求解

这里记录了书上提到的三个例子

  • 钢条切割问题展示了动态规划中等价的两种实现
    • 带备忘的自顶向下
    • 自底向上
  • 矩阵链乘法和最长公共子序列展示了动态规划问题的一般分析方法

1.钢条切割问题

一个长度为n的钢条,n为整数,切割为多个长度仍为整数的子钢条,每种长度的钢条价格不一,有一个记录每种长度的钢条价格的价格表p, 求最优切割方案
由条件可以推导出以下结论

  • 如果长度为n的钢条价格足够高,则无需切割
  • 共有 2 n − 1 2^{n-1} 2n1种不同的切割方案,切割数目m从0到n,每种数目对应 C n − 1 m C_{n-1}^m Cn1m种方法,求和得 2 n − 1 2^{n-1} 2n1
递归实现

思路: 将钢条在下标 i i i处分为左右两部分,(因为总会有一个分割点,不切割的话 i i i=0) ,那么可以只对右边长度为 n − i n-i ni的部分进行递归处理,若用 f ( n ) f(n) f(n)表示长度为 n n n的钢条的最佳切割方案,则有:
f ( n ) = m a x ( p i + f ( n − i ) ) 0 < = i < = n f(n) = max(p_i+f(n-i)) _{0<=i<=n} f(n)=max(pi+f(ni))0<=i<=n
时间复杂度: 由于有 2 n − 1 2^{n-1} 2n1种分法,递归的解法实际上尝试了所有的分法,所以时间复杂度: O ( 2 n − 1 ) O(2^{n-1}) O(2n1)
在这里插入图片描述

分析: 递归的解法之所以慢,是因为在递归时重复求解了子问题,如果使用一个表记录子问题的结果,就可以提升求解效率

动态规划方法实现

1.自顶向下
数组r保存了从0到n每个长度的最优方案,以确保不会重复计算。本例中这种方法分析起来不如自底向上清晰

MEMOIZED-CUT-ROD(p,n)
let r[0...n] be a new array
for i=0 to n:
	r[i] = INT_MIN
return MEMOIZED-CUT-ROD-AUX(p,n,r)

MEMOIZED-CUT-ROD-AUX(p,n,r)
if r[n] >= 0:
	return r[n]
if n == 0:
	q = 0
else:
	q = MIN
	for i=1 to n:
		q = max(q,p[i]+MEMOIZED-CUT-ROD-AUX(p,n-i,r))
r[n]=q
return q

在这里插入图片描述
2.自底向上
自底向上方法同样使用数组r保存从0到n每个长度的最优方案,思路是一样的,但是结构更清晰,上一个方法由于使用了递归,分析起来比较麻烦。

BOTTOM-UP-CUT-ROD(p, n)
let r[0...n] be a new array
r[0]=0
for j=1 to n:
	q = MIN
	for i=1 to j:
		q=max(q,p[i]+r[j-i])
	r[j]=q
return r[n]

在这里插入图片描述

2.矩阵链乘法

在这里插入图片描述

步骤1: 最优方案的结构特征

一个 m ∗ n m*n mn的矩阵和一个 n ∗ k n*k nk的矩阵相乘,需要进行 m n k mnk mnk次乘法运算
假设存在最优划分点k, 那么k的左右两边都是最优划分的

步骤2: 递归求解方案

若用 m [ i , j ] m[i, j] m[i,j]表示计算 A i . . . A j A_i...A_j Ai...Aj的最优解,那么在 i i i j j j范围内寻找一个k, 假设这个k是计算 A i . . . A j A_i...A_j Ai...Aj的最优划分,那么就把问题再次分解成了更小的子问题,且存在下面的递推关系,使用自底向上就可以完成求解
在这里插入图片描述

步骤3: 计算最优代价

输入是一个序列 p = < p 0 , p 1 , p 2 , . . . , p n > p=<p_0,p_1,p_2,...,p_n > p=<p0,p1,p2,...,pn>
使用辅助表 m [ 1... n , 1... n ] m[1...n,1...n] m[1...n,1...n] 保存代价 m [ i , j ] m[i,j] m[i,j]
另一个辅助表 s [ 1... n − 1 , 2... n ] s[1...n-1,2...n] s[1...n1,2...n] 记录最优值 m [ i , j ] m[i,j] m[i,j]对应的分割点k

MATRIX-CHAIN-ORDER(p)
n = p.length - 1
let m[1...n, 1...n] and s[1...n-1, 2...n] be new tables
for i = 1 to n:
	m[i,i] = 0   // 当长度为0时代价为0
for l = 2 to n:  // l是子链的长度
	for i = 1 to n-l+1:
		j = i + l -1
		m[i,j] = MAX
		for k = i to j-1:
			q = m[i, k] + m[k+1,j] + p[i-1]*p[k]*p[j]
			if q < m[i, j]:
				m[i, j] = q
				s[i, j] = k

在这里插入图片描述

从长度为1、2的子链代价开始计算,逐步推导出完整的代价辅助表m

步骤4: 构造最优解
PRINT-OPTIMAL-PARENS(s, i , j)
if i==j:
	print "A[i]"
else:
	print "("
	PRINT-OPTIMAL-PARENS(s, i, s[i,j])
	PRINT-OPTIMAL-PARENS(s, s[i,j]+1,j)
	print ")" 

3.最长公共子序列

  • 子序列: 从原序列的下标集合中选取一部分递增的下标序列,以此构建的子序列
  • 公共子序列: X和Y存在相同的子序列Z
  • 注意点: 这里的子序列未必是原序列中连续的一部分

在这里插入图片描述

步骤1:最长公共子序列特征刻画

在这里插入图片描述

步骤2:递归解

在这里插入图片描述

步骤3: 计算LCS长度
LCS-LENGTH(X, Y)
m = X.length
n = Y.length
let b[1...m,1...n] and c[0...m,0...n] be new tables
for i = 1 to m:
	c[i, 0] = 0
for j = 1 to n:
	c[0,j] = 0
for i = 1 to n:
	for j = 1 to m:
		if x[i] == y[j]:
			c[i,j] = c[i-1,j-1] + 1
			b[i,j] = "\"
		elseif c[i-1,j] >= c[i, j-1]:
			c[i,j] = c[i-1,j]
			b[i,j] = "|"
		else:
			c[i,j] = c[i,j-1]
			b[i,j] = "--"
return c, b

b 的作用是辅助打印出LCS

步骤4: 构造LCS
PRINT-LCS(b,X,i,j)
if i==0 or j==0:
	return None
if b[i, j] == "\":
	PRINT-LCS(b,X,i-1,j-1)
	print x[i]
elseif b[i,j] == "|":
	PRINT-LCS(b,X,i-1,j)
else:
	PRINT-LCS(b,X,i,j-1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值