设计思想
已知起点集合 { S 1 , S 2 , ⋯ , S n } \{\ S_1 , S_2 , \cdots, S_n\ \} { S 1 , S 2 , ⋯ , S n } , 终点集合 { T 1 , T 2 , ⋯ , T m } \{\ T_1 , T_2 , \cdots , T_m\ \} { T 1 , T 2 , ⋯ , T m } 以及中间点集、边集, 求从始点到终点的最短路径
假设起点或终点有 m m m 个, 有 n n n 层, 每个节点都有两条路可走, 那么使用 穷举 时路径大概有 O ( m 2 n ) O(m2^n) O ( m 2 n ) 条
动态规划思想
从终点往前推, 子问题的终点不变, 起点前移
记录 C → T C \rightarrow T C → T 子问题的最短路径( 各节点 ), 如 C 1 → T 1 , C 2 → T 3 , C 3 → T 3 , C 4 → T 4 C_1 \rightarrow T_1, C_2 \rightarrow T_3, C_3 \rightarrow T_3, C_4 \rightarrow T_4 C 1 → T 1 , C 2 → T 3 , C 3 → T 3 , C 4 → T 4
子问题起始点前移, 记录 B → T B \rightarrow T B → T 的最短路径, 如 B 2 → C 1 → T 1 B_2 \rightarrow C_1 \rightarrow T_1 B 2 → C 1 → T 1 等
当前子问题的最优解根据上一个子问题的最优解而定, 即 B 2 → C 1 → T 1 B_2 \rightarrow C_1 \rightarrow T_1 B 2 → C 1 → T 1 是基于最短路径 C 1 → T 1 C_1 \rightarrow T_1 C 1 → T 1 , 以此类推
最后一步最大的子问题即为原始问题
优化原则
一个最优决策序列的 任何子序列 本身一定是相对于子序列的初始和结束状态的 最优 的决策序列
反例
求总长模 10 的最小路径
4 → 5 4 \rightarrow 5 4 → 5 路径模 10 最小的是 u , 2 u,2 u , 2 ( 2 % 10 小于 5 % 10 )
3 → 5 3 \rightarrow 5 3 → 5 路径模 10 最小的是 u , 4 u,4 u , 4 ( 4 % 10 小于 7 % 10 )
2 → 5 2 \rightarrow 5 2 → 5 路径模 10 最小的是 u , 6 u,6 u , 6 ( 6 % 10 小于 9 % 10 )
1 → 5 1 \rightarrow 5 1 → 5 路径模 10 最小的是 d , 1 d,1 d , 1 ( 11 % 10 小于 8 % 10 )
事实上虽然 4 → 5 4 \rightarrow 5 4 → 5 是子序列最优解, 但是 3 → 5 3 \rightarrow 5 3 → 5 已经不是子序列的最优解了, 此时不能使用动态规划
u u u 指 u p up u p , d d d 指 d o w n down d o w n , 指代路径方向
设计要素
矩阵乘法
设 A 1 , A 2 , ⋯ , A n A_1 , A_2 , \cdots , A_n A 1 , A 2 , ⋯ , A n 为矩阵序列, A i A_i A i 为 P i − 1 × P i P_{i-1}×P_i P i − 1 × P i 阶矩阵 , i = 1 , 2 , ⋯ , n i = 1, 2, \cdots , n i = 1 , 2 , ⋯ , n , 确定乘法顺序使得元素 相乘的总次数 最少
输入 P = < 10 , 100 , 5 , 50 > P=<10, 100, 5, 50> P =< 10 , 100 , 5 , 50 > 代表 A 1 = 10 × 100 A_1=10×100 A 1 = 10 × 100 矩阵, A 2 = 100 × 5 A_2=100×5 A 2 = 100 × 5 , A 3 = 5 × 50 A_3=5×50 A 3 = 5 × 50
( A 1 A 2 ) A 3 : 10 × 100 × 5 + 10 × 5 × 50 = 7500 (A_1A_2)A_3 : 10 × 100 × 5 + 10 × 5 × 50 = 7500 ( A 1 A 2 ) A 3 : 10 × 100 × 5 + 10 × 5 × 50 = 7500
A 1 ( A 2 A 3 ) : 10 × 100 × 50 + 100 × 5 × 50 = 75000 A_1(A_2A_3): 10 × 100 × 50 + 100 × 5 × 50 = 75000 A 1 ( A 2 A 3 ) : 10 × 100 × 50 + 100 × 5 × 50 = 75000
枚举算法
n 个矩阵连乘, 使用枚举法时递推公式为
P ( n ) = { 1 n = 1 ∑ k = 1 n − 1 P ( k ) P ( n − k ) n > 1 P(n) =
\begin{cases}
1 & n = 1 \\
\sum_{k=1}^{n-1}P(k)P(n-k) & n \gt 1
\end{cases} P ( n ) = { 1 ∑ k = 1 n − 1 P ( k ) P ( n − k ) n = 1 n > 1
将矩阵划分为左 k 个和右 n-k 个单独计算加括号方式数
P ( n + 1 ) = C ( n ) = 1 n + 1 C 2 n n = Ω ( 4 n n 3 2 ) P(n+1) = C(n) = \frac{1}{n+1}C_{2n}^n = \Omega\left(\frac{4^n}{n^{\frac{3}{2}}}\right) P ( n + 1 ) = C ( n ) = n + 1 1 C 2 n n = Ω ( n 2 3 4 n )
动态规划算法
设 m [ i , j ] m[i,j] m [ i , j ] 表示 A i ∼ A j A_i \thicksim A_j A i ∼ A j 的乘积数, 则
m [ i , j ] = { 0 i = j m i n i ≤ k < j ( m [ i , k ] + m [ k + 1 , j ] + P i − 1 P k P j ) i < j m[i,j] =
\begin{cases}
0 & i = j \\
\underset{i\le k \lt j}{min}(m[i,k] + m[k+1, j] + P_{i-1}P_kP_j) & i < j
\end{cases} m [ i , j ] = ⎩ ⎨ ⎧ 0 i ≤ k < j min ( m [ i , k ] + m [ k + 1 , j ] + P i − 1 P k P j ) i = j i < j
递归实现
if i == j then
return 0
m[i, j] = ∞
for k = i + 1 to j − 1 do
q = RecurMatrixChain(P, i, k) + RecurMatrixChain(P, k+1, j) + p[i−1] * p[k] * p[j]
if q < m[i, j] then
m[i, j] = q
return m[i, j]
T ( n ) ≥ { O ( 1 ) n = 1 1 + ∑ k = 1 n − 1 ( T ( k ) + T ( n − k ) + 1 ) n < 1 T(n) \ge
\begin{cases}
O(1) & n = 1 \\
1 + \sum_{k=1}^{n-1}(T(k) + T(n-k) + 1) & n \lt 1
\end{cases} T ( n ) ≥ { O ( 1 ) 1 + ∑ k = 1 n − 1 ( T ( k ) + T ( n − k ) + 1 ) n = 1 n < 1
当 n > 1 n>1 n > 1 时有
T ( n ) ≥ n + ∑ k = 1 n − 1 T ( k ) + ∑ k = 1 n − 1 T ( n − k ) = n + 2 ∑ k = 1 n − 1 T ( k ) T(n)\ge n + \sum_{k=1}^{n-1}T(k) + \sum_{k=1}^{n-1}T(n-k)=n+2\sum_{k=1}^{n-1}T(k) T ( n ) ≥ n + k = 1 ∑ n − 1 T ( k ) + k = 1 ∑ n − 1 T ( n − k ) = n + 2 k = 1 ∑ n − 1 T ( k )
根据数学归纳法可证明
T ( n ) ≥ 2 n − 1 = Ω ( 2 n ) T(n)\ge 2^{n-1} = \Omega(2^n) T ( n ) ≥ 2 n − 1 = Ω ( 2 n )
迭代实现
每个子问题只计算一次, 从最小的子问题算起
使用 备忘录 记录计算过的结果
for r = 2 to n do
for i = 1 to n − r + 1 do
j = i + r − 1 // 2 ~ n
m[i, j] = ∞
for k = i + 1 to j − 1 do // i + 1 ~ j - 1 ( 不含起始和终点 )
t = m[i, k] + m[k + 1, j] + p[i − 1] * p[k] * p[j]
if t < m[i, j] then
m[i, j] = t
return m[1][n]
复杂度
比较
递归实现
时间复杂性高, 空间消耗较小
子问题被多次重复计算
子问题计算次数呈 指数 增长
迭代实现
时间复杂性较低, 空间消耗多
每个子问题只计算一次
子问题的计算随问题规模呈 多项式 增长
案例
投资问题
m m m 元钱, n n n 项投资, f i ( x ) f_i(x) f i ( x ) 为将 x x x 元投入第 i i i 个项目的效益, 求
m a x f 1 ( x 1 ) + f 2 ( x 2 ) + ⋯ + f n ( x n ) max\ f_1(x_1) + f_2(x_2) + \cdots + f_n(x_n) ma x f 1 ( x 1 ) + f 2 ( x 2 ) + ⋯ + f n ( x n )
x 1 + x 2 + ⋯ + x n = m x_1 + x_2 + \cdots + x_n = m x 1 + x 2 + ⋯ + x n = m
设 F k ( x ) F_k(x) F k ( x ) 表示 x x x 元钱投给前 k k k 个项目的最大效益
F k ( x ) = m a x 0 ≤ x k ≤ x f k ( x k ) + F k − 1 ( x − x k ) F_k(x) = \underset{0 \le x_k \le x}{max}\ f_k(x_k) + F_{k-1}(x-x_k) F k ( x ) = 0 ≤ x k ≤ x ma x f k ( x k ) + F k − 1 ( x − x k )
F 1 ( x ) = f 1 ( x ) F_1(x) = f_1(x) F 1 ( x ) = f 1 ( x )
当 k = 1 k=1 k = 1 , 投资 1 ∼ 5 1 \thicksim 5 1 ∼ 5 元时
F 1 ( 1 ) = 11 , F 1 ( 2 ) = 12 , F 1 ( 3 ) = 13 , F 1 ( 4 ) = 14 , F 1 ( 5 ) = 15 F_1(1)=11, F_1(2)=12, F_1(3)=13, F_1(4)=14, F_1(5)=15 F 1 ( 1 ) = 11 , F 1 ( 2 ) = 12 , F 1 ( 3 ) = 13 , F 1 ( 4 ) = 14 , F 1 ( 5 ) = 15
当 k = 2 k=2 k = 2 时, 投资 1 1 1 元时
F 2 ( 1 ) = m a x f 1 ( 1 ) , f 2 ( 1 ) = 11 F_2(1)=max\ f_1(1),f_2(1) = 11 F 2 ( 1 ) = ma x f 1 ( 1 ) , f 2 ( 1 ) = 11
当 k = 2 k=2 k = 2 时, 投资 2 2 2 元时
F 2 ( 2 ) = m a x f 2 ( 2 ) , F 1 ( 1 ) + f 2 ( 1 ) , F 1 ( 2 ) = 12 F_2(2)=max\ f_2(2), F_1(1)+f_2(1), F_1(2)=12 F 2 ( 2 ) = ma x f 2 ( 2 ) , F 1 ( 1 ) + f 2 ( 1 ) , F 1 ( 2 ) = 12
以此类推
// k = 1 的情况
for j = 1 to m do
F[1][j] = f[1][j]
// k >= 2 的情况
for k = 2 to n do
for i = 1 to m do // 前 k 个项目总投资 1 ~ m 元的情况
max = 0
for j = 0 to i do // 第 k 个项目投资 j 元的情况
if f[k][j] + F[k - 1][i - j] > max then
max = f[k][j] + F[k - 1][i - j]
F[k][i] = max
时间复杂度
W ( n ) = O ( n m 2 ) W(n)=O(nm^2) W ( n ) = O ( n m 2 )
背包问题
一个旅行者准备随身携带一个背包, 可以放入背包的物品有 n n n 种, 每种物品的重量和价值分别为 w j w_j w j , v j v_j v j , 如果背包的最大重量限制是 b b b , 怎样选择放入背包的物品以使得背包的价值最大?
假设 F k ( y ) F_k(y) F k ( y ) 为装前 k k k 种物品, 总重不超过 y y y , 背包的最大价值, 则
F k ( y ) = m a x 0 < x k < ⌊ y / w k ⌋ F k − 1 ( y − x k w k ) + x k v k F_k(y) = \underset{0<x_k< \lfloor y/w_k\rfloor}{max}\ F_{k-1}(y-x_kw_k) + x_kv_k F k ( y ) = 0 < x k < ⌊ y / w k ⌋ ma x F k − 1 ( y − x k w k ) + x k v k
另一种更优的方式, 即判断加和不加时物品的总价值
F k ( y ) = m a x F k − 1 ( y ) , F k ( y − w k ) + v k F_k(y) = max\ F_{k-1}(y), F_k(y-w_k) + v_k F k ( y ) = ma x F k − 1 ( y ) , F k ( y − w k ) + v k
for j = 0 to c do
m[0][j] = 0 // 没有物品时放不了任何东西
for j = 1 to n do
m[j][0] = 0 // 最大重量为 0 时放不了任何东西
for k = 1 to n do // 遍历前 k 个物品
for y = 1 to c do
if w[k] > y then
// 不够装了, 则不装
m[k][y] = m[k - 1][y]
else
// 判断装和不装谁的价值更大
m[k][y] = max(m[k - 1][y], m[k - 1][y - w[k]] + v[k])
// 求放置物品的情况
for k = n to 1 do
if m[k][c] > m[k - 1][c] then
x[k] = 1
c -= w[k]
else
x[k] = 0
return m[n, c]
时间复杂度
最长公共子序列(LCS)
设序列 X , Z X, Z X , Z , 若存在一个严格递增的下标序列 < i 1 , i 2 , ⋯ , i k > <i_1, i_2, \cdots, i_k> < i 1 , i 2 , ⋯ , i k > 使得 z j = x i j , j = 1 , 2 , ⋯ , k z_j = x_{ij}, j=1,2,\cdots,k z j = x ij , j = 1 , 2 , ⋯ , k , 称 Z Z Z 是 X X X 的子序列
如 Z = { B , C , D , B } Z=\{B,C,D,B\} Z = { B , C , D , B } , X = { A , B , C , B , D , A , B } X=\{A,B,C,B,D,A,B\} X = { A , B , C , B , D , A , B } , 严格递增的下标为 < 2 , 3 , 5 , 7 > <2,3,5,7> < 2 , 3 , 5 , 7 >
穷举
设 X = { x 1 , x 2 , ⋯ , x m } X=\{x_1,x_2,\cdots,x_m\} X = { x 1 , x 2 , ⋯ , x m } 以及 Y = { y 1 , y 2 , ⋯ , y n } Y=\{y_1,y_2,\cdots,y_n\} Y = { y 1 , y 2 , ⋯ , y n }
那么 X X X 的子序列有 2 m 2^m 2 m 个 ( 元素出现或不出现在子序列中 ) , 同理 Y Y Y 的子序列有 2 n 2^n 2 n 个
判断最长公共子序列则需要 O ( 2 m × 2 n ) O(2^m×2^n) O ( 2 m × 2 n ) 的时间复杂度
动态规划
若 x m = y n x_m=y_n x m = y n , 则 z k = x m = y n z_k=x_m=y_n z k = x m = y n , Z k − 1 Z_{k-1} Z k − 1 是 X m − 1 X_{m-1} X m − 1 和 Y n − 1 Y_{n-1} Y n − 1 的 LCS
若 x m ≠ y n x_m \neq y_n x m = y n , z k ≠ x m z_k\neq x_m z k = x m , 则 Z Z Z 是 X m − 1 X_{m-1} X m − 1 和 Y Y Y 的 LCS
若 x m ≠ y n x_m \neq y_n x m = y n , z k ≠ y n z_k\neq y_n z k = y n , 则 Z Z Z 是 X X X 和 Y n − 1 Y_{n-1} Y n − 1 的 LCS
递推方程
c [ i , j ] = { 0 i = 0 o r j = 0 c [ i − 1 ] [ j − 1 ] + 1 i , j > 0 , x i = y i m a x c [ i , j − 1 ] , c [ i − 1 , j ] i , j > 0 , x i ≠ y i c[i,j]=\begin{cases}
0 & i = 0\ or\ j = 0 \\
c[i-1][j-1] + 1 & i,j > 0, x_i = y_i \\
max\ c[i,j-1], c[i-1,j] & i,j>0, x_i\neq y_i
\end{cases} c [ i , j ] = ⎩ ⎨ ⎧ 0 c [ i − 1 ] [ j − 1 ] + 1 ma x c [ i , j − 1 ] , c [ i − 1 , j ] i = 0 or j = 0 i , j > 0 , x i = y i i , j > 0 , x i = y i
// 边界情况
for i = 1 to m do
c[0][i] = 0
c[i][0] = 0
for i = 1 to m do
for j = 1 to n do
if x[i] = y[j] then
c[i][j] = c[i - 1][j - 1] + 1
else
c[i][j] = max(c[i - 1][j], c[i][j - 1])
// 获取最长子序列
i = m
j = n
while i > 0 and j > 0
if x[i] = y[j] then
res = x[i] + res
i--
j--
else if c[i][j - 1] <= c[i - 1][j]
i--
else
j--
return c[m][n]
计算时间复杂度 Θ ( n m ) \Theta(nm) Θ ( nm )
构造解时间复杂度 Θ ( m + n ) \Theta(m+n) Θ ( m + n )
空间复杂度 Θ ( m n ) \Theta(mn) Θ ( mn )
最大子段和
给定 n n n 个整数(可以为负数)的序列 < a 1 , a 2 , ⋯ , a n > <a_1, a_2, \cdots, a_n> < a 1 , a 2 , ⋯ , a n > , 求
m a x ( 0 , m a x 1 ≤ i ≤ j ≤ n ∑ k = 1 j a k ) max\left(\ 0, \underset{1\le i \le j \le n}{max} \sum_{k=1}^j a_k \ \right) ma x ( 0 , 1 ≤ i ≤ j ≤ n ma x k = 1 ∑ j a k )
要求 k k k 是连续的
顺序求和
sum = 0
begin = 0
end = 0
for i = 1 to n do
for j = i to n do
s = 0
// 计算 i ~ j 之间子段的和
for k = i to j do
s += a[k]
if s > sum then
sum = s
// 记录字段的起始和结束下标
begin = i
end = j
时间复杂度
分治策略
分别计算左半部分的最大子段和 ls , 右半部分的最大字段和 rs , 以及跨越两部分的最大字段和 sum , 判断谁更大
if left = right then
return max(0, a[left])
mid = (left + right) // 2
ls = maxSubSum(a, left, mid)
rs = maxSubSum(a, mid + 1, right)
for i = left to mid do
s1 += a[i]
for i = mid + 1 to right do
s2 += a[i]
sum = s1 + s2
if ls > sum then
sum = ls
if rs > sum then
sum = rs
return sum
时间复杂度
动态规划
c [ i ] c[i] c [ i ] 指 a [ i ] a[i] a [ i ] 必须在子段末尾的子段和
c [ i + 1 ] = m a x c [ i ] + a [ i + 1 ] , a [ i + 1 ] c[i+1]=max\ c[i] + a[i+1], a[i+1] c [ i + 1 ] = ma x c [ i ] + a [ i + 1 ] , a [ i + 1 ]
如果 c [ i ] > 0 c[i] > 0 c [ i ] > 0 , 无论 a [ i + 1 ] a[i + 1] a [ i + 1 ] 是正是负, c [ i ] + a [ i + 1 ] c[i] + a[i + 1] c [ i ] + a [ i + 1 ] 一定大于 a [ i + 1 ] a[i + 1] a [ i + 1 ] , 故最大子段和是 c [ i ] + a [ i + 1 ] c[i] + a[i + 1] c [ i ] + a [ i + 1 ] , 反之, c [ i ] + a [ i + 1 ] c[i] + a[i + 1] c [ i ] + a [ i + 1 ] 一定小于 a [ i + 1 ] a[i + 1] a [ i + 1 ] , 最大字段和取 a [ i + 1 ] a[i + 1] a [ i + 1 ]
for i = 1 to n do
c[i] = max(a[i], c[i - 1] + a[i])
sum = 0
for i = 1 to n do
sum = max(sum, c[i]) // 在所有子段和中找最大的
return sum
时间复杂度和空间复杂度
进一步优化, 空间复杂度 O ( 1 ) O(1) O ( 1 )
sum = 0
max = 0
for i = 0 to n do
if sum > 0 then
sum += a[i]
else
sum = a[i]
if sum > max then
max = sum
return max
最优二分检索树
设集合 S = { x 1 , x 2 , ⋯ , x n } S = \{x_1,x_2,\cdots,x_n\} S = { x 1 , x 2 , ⋯ , x n } 为有序集 , 将这些存储在一棵二叉搜索树上
在二叉搜索树中查找 x x x 并返回, 存在两种情况
在二叉树中找到对应节点, x = x i x=x_i x = x i
在二叉树叶节点中确定区间, x ∈ ( x i , x i + 1 ) x\in(x_i, x_{i+1}) x ∈ ( x i , x i + 1 )
设查找 x x x 时, 找到元素的概率为 b i b_i b i , 找到区间的概率为 a i a_i a i
S S S 的存取概率分布为
P = ( a 0 , b 1 , a 1 , ⋯ , b n , a n ) P=(a_0, b_1, a_1, \cdots, b_n, a_n) P = ( a 0 , b 1 , a 1 , ⋯ , b n , a n )
因为有的元素在 ( x 0 , x 1 ) (x_0, x_1) ( x 0 , x 1 ) 之间, 因此多了一个 a 0 a_0 a 0 , 其中 x 0 = − ∞ x_0=-\infty x 0 = − ∞ , x n + 1 = + ∞ x_{n+1}=+\infty x n + 1 = + ∞
平均比较次数, 其中 c i , d i c_i,d_i c i , d i 指查找深度
注意深度从 0 还是从 1 开始, 下面的公式从 0 开始
p = ∑ i = 1 n b i ( 1 + c i ) + ∑ j = 0 n a j d j p=\sum_{i=1}^n b_i(1 + c_i)+\sum_{j=0}^n a_jd_j p = i = 1 ∑ n b i ( 1 + c i ) + j = 0 ∑ n a j d j
问题描述
给定数据集 S S S 和相关存取概率分布 P P P , 求一棵最优的(即平均比较次数最少的)二分检索树?
解决思路
子数据集
s [ i , j ] = < x i , x i + 1 , ⋯ , x j > s[i,j] = <x_i, x_{i+1}, \cdots, x_j> s [ i , j ] =< x i , x i + 1 , ⋯ , x j >
与子数据集对应的存取概率分布
p [ i , j ] = < a i − 1 , b i , a i , ⋯ , b j , a j > p[i,j] = <a_{i-1}, b_i, a_i, \cdots, b_j, a_j> p [ i , j ] =< a i − 1 , b i , a i , ⋯ , b j , a j >
p [ i , j ] p[i,j] p [ i , j ] 中存取概率和
w [ i , j ] = ∑ p = i − 1 j a p + ∑ q = i j b q w[i,j]=\sum_{p=i-1}^j a_p + \sum_{q=i}^j b_q w [ i , j ] = p = i − 1 ∑ j a p + q = i ∑ j b q
m [ i , j ] m[i,j] m [ i , j ] 指平均比较次数, 因此递推公式为
m [ i , j ] = m i n i ≤ k ≤ j ( m [ i , k − 1 ] + m [ k + 1 , j ] ) + w [ i , j ] m[i,j]=\underset{i\le k\le j}{min}(\ m[i,k-1]+m[k+1,j]\ ) + w[i,j] m [ i , j ] = i ≤ k ≤ j min ( m [ i , k − 1 ] + m [ k + 1 , j ] ) + w [ i , j ]
合并为一棵新树后, 左右子树的深度都增加了 1 1 1 , 因此加上 w [ i , j ] w[i,j] w [ i , j ]
for i = 0 to n do
w[i + 1][i] = a[i]
m[i + 1][i] = 0
for r = 0 to n do
for i = 1 to n - r do
j = i + r
w[i][j] = w[i][j - 1] + a[j] + b[j]
m[i][j] = m[i + 1][j]
s[i][j] = i
for k = i + 1 to j do
t = m[i][k - 1] + m[k + 1][j]
if t < m[i][j] then
m[i][j] = t
s[i][j] = k // 子树根
m[i][j] += w[i][j]
时间复杂度
生物信息学
RNA 一级结构: 由字母 A,C,G,U 标记的核苷酸构成的一条链
RNA 二级结构: 核苷酸相互匹配
匹配原则
问题描述
给定 RNA 的一级结构, 由 A,U,C,G 构成的长为 n 的 序列, 寻找具有 最大匹配对数 的二级结构
解决思路
c [ i , j ] c[i,j] c [ i , j ] 是序列 s [ i , j ] s[i,j] s [ i , j ] 的最大匹配数, 递推公式如下
c [ i , j ] = m a x ( c [ i , j − 1 ] , m a x i ≤ k < j − 4 ( 1 + c [ i , k − 1 ] + c [ k + 1 , j ] ) ) c[i,j] = max(\ c[i,j-1], \underset{i\le k\lt j-4}{max}(1 + c[i, k-1] + c[k+1,j])\ ) c [ i , j ] = ma x ( c [ i , j − 1 ] , i ≤ k < j − 4 ma x ( 1 + c [ i , k − 1 ] + c [ k + 1 , j ]) )
c [ i , k ] = 0 j − i < 5 c[i,k]=0 \ \ \ j - i < 5 c [ i , k ] = 0 j − i < 5
比较 i , j i,j i , j 配对 ( 1 + c [ i , k − 1 ] + c [ k + 1 , j ] 1 + c[i, k-1] + c[k+1,j] 1 + c [ i , k − 1 ] + c [ k + 1 , j ] )和不配对 ( c [ i , j − 1 ] c[i,j-1] c [ i , j − 1 ] ) 时谁的配对数更大
时间复杂度
序列比对
给定两个序列 S 1 S_1 S 1 和 S 2 S_2 S 2 , 通过一系列字符编辑(插入、删除、替换)等操作, 将 S 1 S_1 S 1 转变成 S 2 S_2 S 2 , 完成这种转换所需要的 最少编辑操作个数 称为 编辑距离
例子
v i n t n e r vintner v in t n er 转变成 w r i t e r s writers w r i t ers , 最少进行六次操作即可完成
解决思路
已知序列 s 1 [ 1 , n ] s_1[1,n] s 1 [ 1 , n ] 和 s 2 [ 1 , m ] s_2[1,m] s 2 [ 1 , m ]
设 c [ i , j ] c[i,j] c [ i , j ] 为序列 s 1 [ 1 , i ] s_1[1,i] s 1 [ 1 , i ] 和 s 2 [ 1 , j ] s_2[1,j] s 2 [ 1 , j ] 的编辑距离, 则递推公式为
c [ i , j ] = m i n ( c [ i − 1 , j ] + 1 , c [ i , j − 1 ] + 1 , c [ i − 1 , j − 1 ] + t [ i , j ] ) c[i,j] = min(\ c[i-1,j] + 1, c[i,j-1]+1,c[i-1,j-1]+t[i,j]\ ) c [ i , j ] = min ( c [ i − 1 , j ] + 1 , c [ i , j − 1 ] + 1 , c [ i − 1 , j − 1 ] + t [ i , j ] )
分别对应删除、插入、替换操作
其中
t [ i , j ] = { 0 s 1 [ i ] = s 2 [ j ] 1 s 1 [ i ] ≠ s 2 [ j ] t[i,j]=\begin{cases}
0 & s_1[i]=s_2[j] \\
1 & s_1[i]\neq s_2[j]
\end{cases} t [ i , j ] = { 0 1 s 1 [ i ] = s 2 [ j ] s 1 [ i ] = s 2 [ j ]
c [ 0 , j ] = j , c [ i , 0 ] = i c[0,j]=j, c[i,0]=i c [ 0 , j ] = j , c [ i , 0 ] = i
时间复杂度
图像压缩
像素点灰度值 0~255 , 使用 8 位二进制数表示
像素点灰度值序列 < p 1 , p 2 , ⋯ , p n > < p_1 , p_2 , \cdots , p_n > < p 1 , p 2 , ⋯ , p n > , p i p_i p i 为第 i i i 个像素点灰度值
变位压缩存储格式
将 < p 1 , p 2 , ⋯ , p n > < p_1 , p_2 , \cdots, p_n > < p 1 , p 2 , ⋯ , p n > 分割 m m m 段 S 1 , S 2 , ⋯ , S m S_1 , S_2 , \cdots , S_m S 1 , S 2 , ⋯ , S m
段头 11 11 11 位, 包括 S i S_i S i 的最大位数(使用 3 位二进制表示, 表示位数不超过 8), 以及段内元素个数(8 位, 表示数量不超过 256)
p = < 10 , 12 , 15 , 255 , 1 , 2 , 1 , 1 , 2 , 2 , 1 , 1 > p=<10,12,15,255,1,2,1,1,2,2,1,1> p =< 10 , 12 , 15 , 255 , 1 , 2 , 1 , 1 , 2 , 2 , 1 , 1 >
可以分割成
S = < 10 , 12 , 15 > , < 255 > , < 1 , 2 , 1 , 1 , 2 , 2 , 1 , 1 > S=<10,12,15>,<255>,<1,2,1,1,2,2,1,1> S =< 10 , 12 , 15 > , < 255 > , < 1 , 2 , 1 , 1 , 2 , 2 , 1 , 1 >
那么存储占用为
s = 4 × 3 + 11 + 8 + 11 + 2 × 8 + 11 = 69 s = 4 × 3 + 11 + 8 + 11 + 2 × 8 + 11 = 69 s = 4 × 3 + 11 + 8 + 11 + 2 × 8 + 11 = 69
问题描述
给定像素序列 < p 1 , p 2 , ⋯ , p n > < p_1 , p_2 , \cdots, p_n > < p 1 , p 2 , ⋯ , p n > , 确定最优分段
解决思路
设 s [ i ] s[i] s [ i ] 是像素序列 < p 1 , p 2 , ⋯ , p i > < p_1 , p_2 , \cdots, p_i > < p 1 , p 2 , ⋯ , p i > 最优分段所需的存储位数
s [ i ] = m i n 1 ≤ k ≤ m i n { i , 256 } s [ i − k ] + k × b [ i − k + 1 , i ] + 11 s[i]=\underset{1\le k \le min\{i,256\}}{min} s[i-k]+k×b[i-k+1,i] + 11 s [ i ] = 1 ≤ k ≤ min { i , 256 } min s [ i − k ] + k × b [ i − k + 1 , i ] + 11
k k k 是指往后将多少个元素归为一类, 最多不超过 256 256 256 , 即 S i S_i S i 元素个数上限
b [ i , j ] = ⌈ l o g ( m a x i ≤ k ≤ j p k + 1 ) ⌉ b[i,j]=\left\lceil log\left(\underset{i\le k\le j}{max}\ p_k+ 1\right) \right\rceil b [ i , j ] = ⌈ l o g ( i ≤ k ≤ j ma x p k + 1 ) ⌉
即 < p i , ⋯ , p j > < p_i , \cdots, p_j > < p i , ⋯ , p j > 中最大值的位数
lmax = 256
header = 11
s[0] = 0
for i = 1 to n do
// bmax 至少等于 pi 的位数
b[i] = len(p[i])
bmax = b[i]
// 假设分段为 <..., p[i - 1]>, <p[i]>
s[i] = s[i - 1] + bmax
l[i] = 1
// 往前归类, 如 <..., p[i - 2]>, <p[i - 1], p[i]>
for k = 2 to min(i, lmax) do
// 分组内单个元素最大占用变大
if bmax < b[i - k + 1] then
bmax = b[i - k + 1]
// 反而使总的占用减小
if s[i] > s[i - k] + k * bmax then
s[i] = s[i - k] + k * bmax
l[i] = k
s[i] += header
时间复杂度