2.2.2 A WFA Example
下面用一个 6 个碱基的 DNA 序列比对 例子,完整走一遍 WFA 的波前扩展和回溯。
1. 选例子
设两条序列:
R = A C G T A C
Q = A C G T C A
索引:
R: R1=A R2=C R3=G R4=T R5=A R6=C
Q: Q1=A Q2=C Q3=G Q4=T Q5=C Q6=A
采用最简单的 Levenshtein 距离:
- 匹配:代价 0
- 错配:代价 1
- 插入:代价 1
- 删除:代价 1
目标:把 R 变成 Q,最少需要几次编辑。
肉眼观察:
R: A C G T A C
Q: A C G T C A
前 4 个 ACGT 完全一样。后面:
R第 5 个是A,第 6 个是CQ第 5 个是C,第 6 个是A
可以这样做:
- 删除
R5=A - 在末尾插入
Q6=A
所以编辑距离是 2。
下面看 WFA 怎么按分数一层层找到这个结果。
2. WFA 基本记号
定义 DP 矩阵中的位置 (i, j):
i:已经用了R的前i个字符j:已经用了Q的前j个字符
定义对角线:
主对角线是 k = 0。
WFA 用波前 X_s(k) 表示:
在分数
s下,对角线k上能到达的最远i坐标。
因为同一条对角线上走得越远越好,所以每条对角线只记最远位置。
关键规则:
- 匹配:留在同一条对角线,免费前进。
- 错配:留在同一条对角线,分数 +1,位置前进 1。
- 插入:跳到
k-1对角线,分数 +1。 - 删除:跳到
k+1对角线,分数 +1。
每次编辑操作后,继续沿对角线做匹配延伸,直到不能匹配为止。
3. 分数 s = 0:只靠匹配能走多远
从 (0,0) 出发,对角线 k = 0。
依次比较:
R1=A vs Q1=A 匹配
R2=C vs Q2=C 匹配
R3=G vs Q3=G 匹配
R4=T vs Q4=T 匹配
R5=A vs Q5=C 不匹配
所以 s = 0 时,主对角线 k = 0 最远到达:
(4,4)
波前表:
| 分数 s | 对角线 k | 最远位置 (i,j) |
|---|---|---|
| 0 | 0 | (4,4) |
终点是 (6,6),还没到。
4. 分数 s = 1:允许一次编辑
从 s = 0 的 (4,4) 出发,考虑一次编辑操作。
4.1 错配:留在 k = 0
R5=A 和 Q5=C 错配,分数 +1,到达:
(5,5)
然后继续匹配:
R6=C vs Q6=A 不匹配
所以 k = 0 上最远到 (5,5)。
4.2 删除:跳到 k = 1
删除 R5=A,相当于 R 前进、Q 不动:
从 (4,4) 到 (5,4),对角线:
然后继续匹配:
R6=C vs Q5=C 匹配
到达 (6,5)。此时 R 结束,Q 还剩 Q6=A,不能继续匹配。
所以 k = 1 上最远到 (6,5)。
4.3 插入:跳到 k = -1
插入 Q5=C,相当于 Q 前进、R 不动:
从 (4,4) 到 (4,5),对角线:
然后继续匹配:
R5=A vs Q6=A 匹配
到达 (5,6)。此时 Q 结束,R 还剩 R6=C,不能继续匹配。
所以 k = -1 上最远到 (5,6)。
4.4 s = 1 的波前
| 分数 s | 对角线 k | 最远位置 (i,j) |
|---|---|---|
| 1 | 1 | (6,5) |
| 1 | 0 | (5,5) |
| 1 | -1 | (5,6) |
终点 (6,6) 还没到。
5. 分数 s = 2:允许两次编辑,到达终点
从 s = 1 的波前继续。
5.1 从 k = 1 的 (6,5) 出发
插入 Q6=A:
从 (6,5) 到 (6,6),对角线:
到达终点 (6,6),分数从 1 增加到 2。
5.2 从 k = -1 的 (5,6) 出发
删除 R6=C:
从 (5,6) 到 (6,6),对角线:
同样到达终点 (6,6),分数从 1 增加到 2。
所以第一次到达终点是在 分数 s = 2。
因此最优 Levenshtein 距离:
6. 波前汇总
| 分数 s | 对角线 k | 最远位置 (i,j) |
|---|---|---|
| 0 | 0 | (4,4) |
| 1 | 1 | (6,5) |
| 1 | 0 | (5,5) |
| 1 | -1 | (5,6) |
| 2 | 0 | (6,6) |
可以看到,WFA 不是填满整个 6×6 矩阵,而是只计算到分数 2 的波前。
7. 回溯:找回具体比对
我们选一条路径:
从终点 (6,6) 回溯。
在 s = 2,到达 (6,6) 的操作是从 s = 1 的 k = 1 的 (6,5) 插入 Q6=A。
所以指针指向:
s=1, k=1, (6,5)
在 s = 1,(6,5) 是怎么来的?
是从 s = 0 的 (4,4) 删除 R5=A 到 (5,4),然后匹配 R6=C 和 Q5=C 延伸到 (6,5)。
所以指针指向:
s=0, k=0, (4,4)
在 s = 0,从 (4,4) 沿对角线反向匹配:
R4=T vs Q4=T
R3=G vs Q3=G
R2=C vs Q2=C
R1=A vs Q1=A
回到 (0,0)。
8. 最终比对
把回溯路径展开:
R: A C G T A C -
Q: A C G T - C A
对应关系:
| 位置 | R | Q | 操作 |
|---|---|---|---|
| 1 | A | A | 匹配 |
| 2 | C | C | 匹配 |
| 3 | G | G | 匹配 |
| 4 | T | T | 匹配 |
| 5 | A | - | 删除 |
| 6 | C | C | 匹配 |
| 7 | - | A | 插入 |
编辑操作:
- 删除
R5=A - 插入
Q6=A
总共 2 次编辑,Levenshtein 距离 = 2。
9. 这个例子说明了什么?
- WFA 按分数
s = 0, 1, 2, …一层层扩展波前。 - 匹配是免费的,所以会沿对角线一直跳。
- 错配、插入、删除各让分数 +1,并切换到相邻或同一条对角线。
- 每条对角线只保留最远位置。
- 第一次到达终点时的分数就是最优编辑距离。
- 三角回溯矩阵记录每个格子来自哪条对角线、哪个分数,从而可以重建比对。
这个 6 碱基例子虽然简单,但已经包含了 WFA 的核心机制:波前、对角线、匹配延伸、编辑操作、回溯。TALCO 后面利用的正是这些回溯路径在 tile 边界上的汇聚性质。