概述
文章目录
- 1. 切割绳子问题
- 2. 最长公共子序列问题(LCS)
- 3. 图像压缩
- 4. 最优二叉检索树
- 5. RNA二级结构预测
- 背包问题
- 序列DP(线性DP)
- 区间DP
- 树上DP
- 状态压缩DP
动态规划的基本原则:最优子结构
1. 切割绳子问题
设定:给定总绳子长度,以及长度为i的绳子对应的价值为p[i];求如何分割使得总价值最大。
长度为i的绳子;可分j段
d
p
[
i
]
=
max
1
≤
j
≤
i
(
d
p
[
i
−
j
]
+
p
[
j
]
)
dp[i] = maxlimits_{1le j le i} (dp[i-j]+p[j])
dp[i]=1≤j≤imax(dp[i−j]+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[l1−1][j] 和 d p [ i ] [ l 2 − 1 ] dp[i][l_2-1] dp[i][l2−1]
-
使用从前向后的正向顺序也是可以的。
-
循环体内的时间复杂度为 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(i−1,j−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
sumlimits_{i=1}^m len[i]cdot bit[i]+11m
i=1∑mlen[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(maxlimits_{p_k in s_t}p_k+1)rceil le 8
bit[t]=⌈log2(pk∈stmaxpk+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] = minlimits_{1le j le i}{dp[j]+(i-j+1)cdot lceil log_2(maxlimits_{j+1le k le i}p_k+1)rceil +header}
dp[i+1]=1≤j≤imin{dp[j]+(i−j+1)⋅⌈log2(j+1≤k≤imaxpk+1)⌉+header}
具体实现:对固定i之后的内部循环,可用bmax记录当前像素的最大值(最后向前,依次比较更新bmax值)
追踪解:向前追踪,用标记数组记录总长度取最小值时最后一段的长度。
4. 最优二叉检索树
需要考虑数据元素存取的概率分布
输入:给定序列 S = { x i , 1 ≤ i ≤ n } S={x_i,1le i le n} S={xi,1≤i≤n}。记 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} ai−1和 a j a_j aj (将边界外的值加总,即 a i − 1 ′ = ∑ k = 1 i − 1 a k a_{i-1}' = sumlimits_{k=1}^{i-1}a_k ai−1′=k=1∑i−1ak);但实际上只需要截取部分概率即可,也便于计算,这是因为合并子问题时可以直接合并边界。
写出状态转移方程如下:
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_{ile k le j} { dp[i][k-1] +p[i][k-1]+b_k+dp[k+1][j]+p[k+1][j] } \ &= min_{ile k le j} { dp[i][k-1]+dp[k+1][j]+p[i][j] } end{aligned}
dp[i][j]=i≤k≤jmin{dp[i][k−1]+p[i][k−1]+bk+dp[k+1][j]+p[k+1][j]}=i≤k≤jmin{dp[i][k−1]+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})
(xi−1,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})
(xk−1,xl+1)的概率,有:
p
[
k
]
[
l
]
=
∑
i
=
k
−
1
l
a
i
+
∑
j
=
k
l
b
j
p[k][l] = sumlimits_{i =k-1}^{l}a_i +sumlimits_{j=k}^l b_j
p[k][l]=i=k−1∑lai+j=k∑lbj
- 注意:边界条件为 d p [ i ] [ i − 1 ] = 0 dp[i][i-1] = 0 dp[i][i−1]=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_{ile k le j-4}1+C[i][k-1]+C[k+1][j-1]} C[i][j]=max{c[i][j−1],maxi≤k≤j−41+C[i][k−1]+C[k+1][j−1]}
总结
-
引入参数,界定子问题边界
-
设计优化函数,验证是否满足优化原则
-
用备忘录记录优化函数的值,自底向上迭代计算
-
用标记函数进行记录,从而便于追踪最优解。
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],0le k le dfrac j{w[i]} dp[i][j]=dp[i−1][j−k∗w[i]]+k∗v[i],0≤k≤w[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[i−1][j],dp[i][j−w[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[j−w[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] ∣ rj≤li}
优化:只需要找到最大的 j
区间DP
左右端点
石子归并:必须合并相邻的石子
树上DP
二叉苹果树问题:通过两个子树的苹果数进行递推
F [ i ] [ j ] F[i][j] F[i][j] 表示根结点为i,保留j条树枝所能保留的最大苹果树。从而可以进行递推。
状态压缩DP
思路:将问题状态压缩成二进制数
例:最小哈密顿路径问题(TSP问题)
最后
以上就是和谐摩托为你收集整理的【算法设计与分析】动态规划算法学习笔记的全部内容,希望文章能够帮你解决【算法设计与分析】动态规划算法学习笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复