【匹配】Hirschberg
Hirschberg
文章目录
- Hirschberg
- 1. 算法介绍
- 2. 公式及原理
- 3. 伪代码
1. 算法介绍
-
背景与目标
Hirschberg 算法由 Dan Hirschberg 于1975年提出,是对 Needleman–Wunsch 全局比对的内存优化,通过分治策略将空间复杂度从 O ( m n ) O(mn) O(mn) 降到 O ( min ( m , n ) ) O(\min(m,n)) O(min(m,n)),但仍保持 O ( m n ) O(mn) O(mn) 的时间复杂度。核心目标:在内存受限的情况下,仍能获得两条序列的最优全局比对结果。
-
应用场景
- 在大规模或内存受限环境(如嵌入式设备)中做序列全局比对
- 对长序列(数万甚至数十万长度)进行比对而不爆内存
- 作为组合更复杂比对算法的空间优化子例程
-
核心思路
- 线性空间动态规划——用两行滚动数组计算 Needleman–Wunsch 得分向量;
- 分治分割——将序列 A A A 在中点处分成左右两段,分别对左半段与 B B B 做“正向” DP,对右半段与 B B B 做“反向” DP;
- 寻找最优分割点——在 B B B 上找到使前后两段得分和最大的分割索引;
- 递归对齐——分治地对两对子序列递归调用 Hirschberg,还原最终全局对齐。
2. 公式及原理
2.1 线性空间 DP
对长度为 m m m 的序列 A A A 和长度为 n n n 的序列 B B B,标准 NW 填表需要维护 ( m + 1 ) × ( n + 1 ) (m+1)\times(n+1) (m+1)×(n+1) 矩阵。Hirschberg 只保留当前行和上一行:
score curr [ j ] = max { score prev [ j − 1 ] + s ( a i , b j ) , score prev [ j ] − d , score curr [ j − 1 ] − d . \text{score}_{\text{curr}}[j] = \max\!\begin{cases} \text{score}_{\text{prev}}[j-1] + s(a_i, b_j),\\ \text{score}_{\text{prev}}[j] - d,\\ \text{score}_{\text{curr}}[j-1] - d. \end{cases} scorecurr[j]=max⎩ ⎨ ⎧scoreprev[j−1]+s(ai,bj),scoreprev[j]−d,scorecurr[j−1]−d.
这样每次只用 O ( n ) O(n) O(n) 空间计算出一行得分向量。
2.2 分治分割
将 A A A 分为两半 A L = A [ 1.. ⌊ m / 2 ⌋ ] A_{\text{L}}=A[1..\lfloor m/2\rfloor] AL=A[1..⌊m/2⌋] 和 A R = A [ ⌊ m / 2 ⌋ + 1.. m ] A_{\text{R}}=A[\lfloor m/2\rfloor+1..m] AR=A[⌊m/2⌋+1..m]。
- 正向得分:对 A L A_{\text{L}} AL 和 B B B 用线性空间 DP,得到得分向量 F [ j ] F[j] F[j](代表对齐到 B [ 1.. j ] B[1..j] B[1..j] 的最优得分)。
- 反向得分:对反转后的 A R A_{\text{R}} AR 和反转后的 B B B 做同样 DP,得到向量 G [ j ] G[j] G[j](对应对齐到尾部 B [ j + 1.. n ] B[j+1..n] B[j+1..n])。
- 合并:在所有划分点 j ∈ [ 0.. n ] j\in[0..n] j∈[0..n] 上计算 F [ j ] + G [ n − j ] F[j]+G[n-j] F[j]+G[n−j],取最大者 j ∗ j^* j∗ 作为最佳分割。
2.3 递归重构
按分割索引将问题分为两对子序列 ( A L , B [ 1.. j ∗ ] ) (A_{\text{L}}, B[1..j^*]) (AL,B[1..j∗]) 和 ( A R , B [ j ∗ + 1.. n ] ) (A_{\text{R}}, B[j^*+1..n]) (AR,B[j∗+1..n]),递归调用 Hirschberg;当子问题中某条序列长度为 0 或 1 时,退化回标准 Needleman–Wunsch 或简单对齐,完成回溯。
3. 伪代码
# 输入
# A[1..m], B[1..n]
# s(a,b): 匹配得分函数
# d: 线性 gap penalty
# 输出
# aligned_A, aligned_Bfunction Hirschberg(A, B):m ← length(A); n ← length(B)# 基本情况:若一条序列长度 ≤ 1,用标准 NW 直接对齐if m == 0:return repeat('-', n), Bif n == 0:return A, repeat('-', m)if m == 1 or n == 1:return NeedlemanWunsch(A, B, s, d)# 1) 分割 Amid ← ⌊m/2⌋A_L ← A[1..mid]; A_R ← A[mid+1..m]# 2) 正向 DP:A_L vs B → F[0..n]F ← NW_ScoreVector(A_L, B, s, d)# 3) 反向 DP:reverse(A_R) vs reverse(B) → G[0..n]G_rev ← NW_ScoreVector(reverse(A_R), reverse(B), s, d)G ← reverse(G_rev)# 4) 找到最佳分割点 j*best_j, _ ← argmax_{j=0..n} (F[j] + G[j])# 5) 递归对齐左右两段(A1, B1) ← Hirschberg(A_L, B[1..best_j])(A2, B2) ← Hirschberg(A_R, B[best_j+1..n])return (A1 ∥ A2), (B1 ∥ B2)# 辅助:计算 DP 得分向量(只返回最后一行)
function NW_ScoreVector(X, Y, s, d):n ← length(Y)prev[0..n] ← [0, -d, -2d, …, -n·d]for i in 1..length(X):curr[0] ← -i·dfor j in 1..n:match ← prev[j-1] + s(X[i], Y[j])delete ← prev[j] - dinsert ← curr[j-1] - dcurr[j] ← max(match, delete, insert)prev ← currreturn prev
- 时间复杂度: O ( m × n ) O(m \times n) O(m×n)
- 空间复杂度: O ( n ) O(n) O(n)(额外递归栈 O ( log m ) O(\log m) O(logm),总 O ( n + log m ) O(n + \log m) O(n+logm))