【算法设计与分析】动态规划算法学习笔记


动态规划的基本原则:最优子结构


1. 切割绳子问题

设定:给定总绳子长度,以及长度为i的绳子对应的价值为p[i];求如何分割使得总价值最大。

长度为i的绳子;可分j段

d p [ i ] = max ⁡ 1 ≤ j ≤ i ( d p [ i − j ] + p [ j ] ) dp[i] = \max\limits_{1\le j \le i} (dp[i-j]+p[j]) dp[i]=1jimax(dp[ij]+p[j])
最优子结构:局部最优最终会保证整体最优。

动规重要特征:将递归转化为非递归,从而降低时间复杂度(子问题只计算一次)(用记忆化数组进行存储;从而用空间换时间)



2. 最长公共子序列问题(LCS)
  • 注意:子序列不一定要在原序列中连续排列

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示序列1从位置i开始,序列2从位置j开始的最长公共子序列长度。
d p [ i ] [ j ] = { d p [ i + 1 ] [ j + 1 ] + 1 , i f   s 1 [ i ] = s 2 [ j ] max ⁡ ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) , e l s e dp[i][j] = \begin{cases}dp[i+1][j+1]+1 , if \space s_1[i] = s_2[j] \\ \\ \max(dp[i+1][j],dp[i][j+1]),else \end{cases} dp[i][j]=dp[i+1][j+1]+1,if s1[i]=s2[j]max(dp[i+1][j],dp[i][j+1]),else

  • 初始化(边界条件): d p [ l 1 − 1 ] [ j ] dp[l_1-1][j] dp[l11][j] d p [ i ] [ l 2 − 1 ] dp[i][l_2-1] dp[i][l21]

  • 使用从前向后的正向顺序也是可以的。

  • 循环体内的时间复杂度为 O ( m n ) O(mn) O(mn)


Q:如果想要输出最长公共子序列呢?

使用标记函数:每个子问题取最优时的情况(三种分类)

追踪解:通过标记数组B的情况递归调用数组 Search(i,j)

1)若是第一种情形( s 1 [ i ] = s 2 [ j ] s_1[i] = s_2[j] s1[i]=s2[j]),则直接输出 s 1 [ i ] s_1[i] s1[i],然后继续调用 s e a r c h ( i − 1 , j − 1 ) search(i-1,j-1) search(i1,j1);

  1. 若为第二/三种情形,则分别调用search(i,j-1)或search(i-1,j)
  • 可知追踪解的时间复杂度为 O ( m + n ) O(m+n) O(m+n)

3. 图像压缩

黑白图像存储:灰度值序列(每个像素的灰度值占8位,0-255)

Q:经常出现大面积相同颜色的区域,能否用更好的方式来节省存储空间?灰度值很低的地方是否只需要更少的位来存储?


变位压缩:把灰度值序列分为若干段,同一段的灰度值用相同的位数存储

对第t段而言,需要记录该段像素个数len[t]和每个像素占用位数bit[t] (共11位在段头记录,考虑每段的额外开销)

总占用位数为: ∑ i = 1 m l e n [ i ] ⋅ b i t [ i ] + 11 m \sum\limits_{i=1}^m len[i]\cdot bit[i]+11m i=1mlen[i]bit[i]+11m

Q:如何确定序列的划分来最小化总位数?

组合优化问题:约束条件: b i t [ t ] = ⌈ log ⁡ 2 ( max ⁡ p k ∈ s t p k + 1 ) ⌉ ≤ 8 bit[t] = \lceil \log_2(\max\limits_{p_k \in s_t}p_k+1)\rceil \le 8 bit[t]=log2(pkstmaxpk+1)8

Q:如何界定子问题?
d p [ i + 1 ] = min ⁡ 1 ≤ j ≤ i { d p [ j ] + ( i − j + 1 ) ⋅ ⌈ log ⁡ 2 ( max ⁡ j + 1 ≤ k ≤ i p k + 1 ) ⌉ + h e a d e r } dp[i+1] = \min\limits_{1\le j \le i}\{dp[j]+(i-j+1)\cdot \lceil \log_2(\max\limits_{j+1\le k \le i}p_k+1)\rceil +header\} dp[i+1]=1jimin{dp[j]+(ij+1)log2(j+1kimaxpk+1)+header}
具体实现:对固定i之后的内部循环,可用bmax记录当前像素的最大值(最后向前,依次比较更新bmax值)

追踪解:向前追踪,用标记数组记录总长度取最小值时最后一段的长度。


4. 最优二叉检索树

需要考虑数据元素存取的概率分布

输入:给定序列 S = { x i , 1 ≤ i ≤ n } S=\{x_i,1\le i \le n\} S={xi,1in}。记 x x x x i x_i xi的概率为 b i b_i bi x x x落在 ( x i , x i + 1 ) (x_i,x_{i+1}) (xi,xi+1)内的概率为 a i a_i ai。即用 { a 0 , a 1 , … , a n + 1 , b 1 , b 2 , … , b n } \{a_0,a_1,\dots,a_{n+1},b_1,b_2,\dots,b_n\} {a0,a1,,an+1,b1,b2,,bn}存储S的概率分布。

目标:给出已知概率下平均次数最小的BST(如何组织结点 x i x_i xi


子问题划分:(利用树的递归定义,讨论根结点,左右子树深度+1)

起初想法:如考虑子问题边界(i,j),则需改变概率 a i − 1 a_{i-1} ai1 a j a_j aj (将边界外的值加总,即 a i − 1 ′ = ∑ k = 1 i − 1 a k a_{i-1}' = \sum\limits_{k=1}^{i-1}a_k ai1=k=1i1ak);但实际上只需要截取部分概率即可,也便于计算,这是因为合并子问题时可以直接合并边界。


写出状态转移方程如下:
d p [ i ] [ j ] = min ⁡ i ≤ k ≤ j { d p [ i ] [ k − 1 ] + p [ i ] [ k − 1 ] + b k + d p [ k + 1 ] [ j ] + p [ k + 1 ] [ j ] } = min ⁡ i ≤ k ≤ j { d p [ i ] [ k − 1 ] + d p [ k + 1 ] [ j ] + p [ i ] [ j ] } \begin{aligned} dp[i][j] &= \min_{i\le k \le j} \{ dp[i][k-1] +p[i][k-1]+b_k+dp[k+1][j]+p[k+1][j] \} \\ &= \min_{i\le k \le j} \{ dp[i][k-1]+dp[k+1][j]+p[i][j] \} \end{aligned} dp[i][j]=ikjmin{dp[i][k1]+p[i][k1]+bk+dp[k+1][j]+p[k+1][j]}=ikjmin{dp[i][k1]+dp[k+1][j]+p[i][j]}
其中 d p [ i ] [ j ] dp[i][j] dp[i][j] 考虑的即为子区间 ( x i − 1 , x j + 1 ) (x_{i-1},x_{j+1}) (xi1,xj+1) 上的情况,最终目标为 d p [ 1 ] [ n ] dp[1][n] dp[1][n] p [ k ] [ l ] p[k][l] p[k][l]表示取值为 ( x k − 1 , x l + 1 ) (x_{k-1},x_{l+1}) (xk1,xl+1)的概率,有:
p [ k ] [ l ] = ∑ i = k − 1 l a i + ∑ j = k l b j p[k][l] = \sum\limits_{i =k-1}^{l}a_i +\sum\limits_{j=k}^l b_j p[k][l]=i=k1lai+j=klbj

  • 注意:边界条件为 d p [ i ] [ i − 1 ] = 0 dp[i][i-1] = 0 dp[i][i1]=0!(从而无需对 j = i+1的情形进行单独讨论)

时间复杂度为 O ( n 3 ) O(n^3) O(n3)。本题思路和矩阵链乘法问题类似。



5. RNA二级结构预测

输入:由A C G U标记的核苷酸构成的一条链。

二级结构:核苷酸相互匹配构成的平面结构(有臂状、环状)

目标:希望预测RNA的平面二级结构

原则:碱基配对;末端不出现“尖角”;只能单向配对;不能交叉

  • 对子问题dp[i] [j],考虑所有能和j位置元素配对的k位置,拆分为两个子问题

C [ i ] [ j ] = m a x { c [ i ] [ j − 1 ] , max ⁡ i ≤ k ≤ j − 4 1 + C [ i ] [ k − 1 ] + C [ k + 1 ] [ j − 1 ] } C[i][j] = max\{c[i][j-1],\max_{i\le k \le j-4}1+C[i][k-1]+C[k+1][j-1]\} C[i][j]=max{c[i][j1],maxikj41+C[i][k1]+C[k+1][j1]}


总结

  • 引入参数,界定子问题边界

  • 设计优化函数,验证是否满足优化原则

  • 用备忘录记录优化函数的值,自底向上迭代计算

  • 用标记函数进行记录,从而便于追踪最优解。

DP算法时间效率高的根本原因:“空间换时间”,当空间要求较大时则往往成为制约因素。


背包问题

空间复杂度的降低:01背包可转化为一维滚动数组,逆向求解

多重背包:转化为0/1背包

状态转移方程: d p [ i ] [ j ] = d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] , 0 ≤ k ≤ j w [ i ] dp[i][j] = dp[i-1][j-k*w[i]]+k*v[i],0\le k \le \dfrac j{w[i]} dp[i][j]=dp[i1][jkw[i]]+kv[i],0kw[i]j

优化:可以使用二进制进行优化


完全背包:每种物品可以放入无限件

因此写出状态转移方程如下: d p [ i ] [ j ] = max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] ) dp[i][j] = \max(dp[i-1][j],dp[i][j-w[i]]+v[i]) dp[i][j]=max(dp[i1][j],dp[i][jw[i]]+v[i])

此处的不同:在考虑是否选择第i件物品这个策略时,不需要限制子结果来自选择前i-1物品(每个物品可以选择无限件)

使用滚动数组进行优化可得:
f o r   i : 1   t o   n   d o    f o r   j : 0   t o   v   d o     d p [ j ] = max ⁡ ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) for \ i : 1 \ to \ n \ do \\ \ \ for \ j : 0\ to \ v \ do \\ \ \ \ dp[j] = \max(dp[j],dp[j-w[i]]+v[i]) for i:1 to n do  for j:0 to v do   dp[j]=max(dp[j],dp[jw[i]]+v[i])
理解:此处滚动数组的遍历顺序是正向的。


序列DP(线性DP)

线段覆盖问题

每条线段具有价值,想选择出若干个互不相交的线段,使得总价值最大。

普通覆盖问题:退化为贪心(活动选择问题)

预处理方法:按照右端点排序?(从第一个开始)

状态转移方程:
F [ i ] = max ⁡ { F [ j ] + v [ i ]   ∣   r j ≤ l i } F[i] = \max\{F[j]+v[i] \ | \ r_j \le l_i \} F[i]=max{F[j]+v[i]  rjli}
优化:只需要找到最大的 j


区间DP

左右端点

石子归并:必须合并相邻的石子


树上DP

二叉苹果树问题:通过两个子树的苹果数进行递推

F [ i ] [ j ] F[i][j] F[i][j] 表示根结点为i,保留j条树枝所能保留的最大苹果树。从而可以进行递推。


状态压缩DP

思路:将问题状态压缩成二进制数

例:最小哈密顿路径问题(TSP问题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值