# 3. Left Shift
在 Bitap 算法中,左移一位表示消耗一个模式字符,这是因为状态位向量中每一位的位置与模式中已匹配的字符数直接对应,而左移操作正是将模式相对于文本向右滑动一个字符。
下面从位向量的定义、对齐关系和位运算语义三方面解释。
1. 位向量的位与模式字符的对应关系
设模式长度为 。状态位向量 有 位。
通常约定:
- 位 0(LSB) 对应模式的第 1 个字符(最左端字符)。
- 位 1 对应模式的第 2 个字符。
- ……
- 位 (MSB) 对应模式的第 个字符(最右端字符)。
例如模式 ,:
| 位 | 对应模式字符 |
|---|---|
| 位 0 (LSB) | 'A'(第 1 个) |
| 位 1 (MSB) | 'C'(第 2 个) |
位值为 0 表示匹配,1 表示不匹配。
2. 文本遍历方向与模式滑动
Bitap 从文本末尾向前遍历(for i in (n-1):-1:0)。
当处理完文本位置 后,下一步处理位置 。
这意味着文本向左移动了一个位置,而模式相对于文本就相当于向右滑动了一个字符。
假设当前状态 表示:从文本位置 开始,模式的前若干个字符已经与文本匹配。
现在我们要处理文本位置 ,需要将模式向右滑动一格,使得模式的第 1 个字符与新的文本位置 对齐。
这个“模式向右滑动一格”在位向量中的表现就是:
原来表示“模式前 个字符匹配”的位(位于位 ),现在应该表示“模式前 个字符匹配”,因此它需要向左移动一位(从位 移到 位 )。同时,最低位(位 0)补 0,表示还没有字符与新的文本位置匹配。
因此,左移一位 = 模式相对于文本向右滑动一个字符 = 消耗了一个模式字符。
3. 结合 Algorithm 1 的具体位运算
以精确匹配为例(Line 13):
oldR[0]是上一轮文本位置的状态。oldR[0] << 1:将旧状态左移一位。
原来在位 的匹配信息(表示模式前 个字符已匹配)移动到 位 ,表示模式前 个字符已匹配。
最低位补 0,准备接收新模式字符的匹配信息。curPM:当前文本字符对应的模式位掩码。
curPM的位 0 表示当前文本字符是否与模式的第 1 个字符匹配;位 1 表示是否与第 2 个字符匹配……
通过与curPM做 OR,将当前文本字符与模式第 1 个字符的匹配情况填入最低位。
所以,左移操作完成了“模式向右滑动一格”,即消耗了一个模式字符。
4. 用论文 Figure 3 的例子验证
论文 Figure 3 中模式为 "AC",文本为 "AGC",。
初始 (两位全 1,无匹配)。
处理文本最后一个字符 'C'(位置 2):
oldR[0] = 11oldR[0] << 1 = 10(左移一位,MSB 溢出,LSB 补 0)curPM = PM['C'] = 01(位 1 为 0,表示'C'与模式第 2 个字符匹配;位 0 为 1,表示与第 1 个字符不匹配)R[0] = 10 | 01 = 11
此时 R[0] 的位 1 为 1,表示从位置 2 开始,模式前两个字符未完全匹配。
左移操作把上一轮可能存在的部分匹配信息向左推进了一位,对应消耗了一个模式字符。
5. 为什么不是右移?
如果模式是向右滑动,在位向量中对应的是左移,因为位向量的索引方向与模式字符顺序一致(位 0 对应第一个字符)。
若采用相反的定义(位 0 对应最后一个字符),则左移和右移的含义会互换。但论文中明确采用“0 表示匹配”,且算法中左移操作与 curPM 的 OR 结合,符合上述解释。
总结
| 操作 | 含义 |
|---|---|
| 左移一位 | 模式相对于文本向右滑动一个字符,即消耗一个模式字符 |
| 最低位补 0 | 为新的文本位置与模式第一个字符的匹配预留位置 |
与 curPM 做 OR | 将当前文本字符与模式各字符的匹配情况填入对应位 |
因此,左移一位表示消耗一个模式字符,这是由位向量中位的位置与模式字符索引的对应关系以及文本遍历方向共同决定的。在 GenASM 中,由于支持长读长,位向量可能跨多个机器字,左移操作需要处理跨字的进位位,这也是论文第 5 节讨论的算法修改之一。