模式匹配自动机全面理论分析
模式匹配是什么
模式匹配是计算机科学中一个基础且重要的问题,广泛应用于文本编辑、信息检索、网络安全、生物信息学等多个领域。简单来说,模式匹配就是在一个主文本中查找一个或多个特定模式串的出现位置。随着计算机处理能力的提升和数据规模的扩大,高效的模式匹配算法变得尤为重要。
常见模式匹配算法
BF 模式匹配算法
算法原理
BF 算法(Brute Force Algorithm),也称为暴力匹配算法或朴素匹配算法,是最基础、最简单的字符串匹配方法。其基本思想是:从主文本的第一个字符开始,与模式串的第一个字符进行比较,如果相等,则继续逐个比较后续字符;如果不相等,则将主文本的比较位置回退到起始位置的下一个字符,模式串的比较位置重置为起始字符,重新开始比较。
BF 算法的设计思想非常直观,没有任何技巧,就是简单粗暴地将主文本和模式串中的字符一一比对,直到找到匹配的位置或遍历完整个主文本。这种算法的匹配顺序是按照模式串的下标从小到大的顺序,依次与主文本中的字符进行匹配的。
算法流程
BF 算法的具体步骤如下:
- 初始化两个指针 i 和 j,分别指向主文本和模式串的起始位置(通常为 0)。
- 进入循环,当 i 小于主文本长度且 j 小于模式串长度时:
a. 如果主文本第 i 个字符与模式串第 j 个字符相等,则 i 和 j 都向后移动一位。
b. 如果不相等,则将 i回退到起始位置的下一个字符(即 i = i - j + 1),j 重置为 0。
- 当 j 等于模式串长度时,说明匹配成功,返回 i - j 作为匹配的起始位置。
- 如果循环结束后仍未找到匹配,则返回 - 1 表示匹配失败。
RK 模式匹配算法
算法原理
RK 算法(Rabin-Karp Algorithm)由 Michael O. Rabin 和 Richard M. Karp 发明,它使用哈希技术来快速找到可能的匹配位置,然后再进行字符比较验证。RK 算法基于 BF 算法进行优化,利用哈希算法将主文本中的 n-m+1 个子串分别求哈希值,然后利用哈希值进行比较,这样省去了逐个字符比较的时间。
RK 算法的核心思想是:
- 计算模式串的哈希值。
- 计算主文本中每个与模式串长度相同的子串的哈希值。
- 比较模式串哈希值与子串哈希值,若相等则进行字符级比较验证。
- 若验证通过,则找到匹配位置;否则继续检查下一个子串。
滚动哈希技术
RK 算法的关键在于使用滚动哈希(Rolling Hash)技术,将每次计算子串哈希值的时间复杂度从 O (m) 降到了 O (1),从而提升了整个算法效率。滚动哈希算法利用了子串中每一位字符的哈希值,并且可以根据上一个子串的哈希值,快速计算相邻子串的哈希值。
假设主文本为 T,模式串为 P,长度分别为 n 和 m。滚动哈希的基本原理如下:
- 选择一个基数 d(通常取字符集大小,如 26)和一个大质数 q(用于取模防止溢出)。
- 模式串 P 的哈希值计算方式为:
Hash(P) = P_0 × d^(m-1) + P_1 × d^(m-2) + … + P_{m-1} × d^0
- 主文本中起始于位置 i 的子串 T [i…i+m-1] 的哈希值可以通过前一个子串的哈希值计算得到:
Hash(T[i+1..i+m]) = [Hash(T[i..i+m-1]) - T[i] × d^(m-1)] × d + T[i+m]
- 每次计算新的哈希值时,都需要对结果取模 q,以防止数值过大。
哈希碰撞
在 RK 算法中,使用滚动哈希技术存在潜在的 “失误” 可能,其核心原因是哈希碰撞(Hash Collision)
哈希函数的基本特性是将任意长度的输入(如字符串)映射到一个固定范围的输出(如取模后的整数)。由于可能的输入(子串)数量远大于输出范围(0 到 q-1),根据 “鸽巢原理”,必然存在不同的子串被映射到相同的哈希值 —— 这就是哈希碰撞。
算法流程
RK 算法的具体步骤如下:
- 计算模式串 P 的哈希值 hash_p。
- 计算主文本 T 中第一个子串(长度为 m)的哈希值 hash_t。
- 计算 d^(m-1) mod q,用于后续哈希值的更新。
- 遍历主文本中所有可能的子串起始位置 i(从 0 到 n-m):
a. 如果 hash_p == hash_t,进行字符级比较验证。
b. 如果验证通过,返回 i 作为匹配起始位置。
c. 如果 i < n-m,计算下一个子串的哈希值:
hash_t = (hash_t - ord(T[i]) × power) % q
hash_t = (hash_t × d + ord(T[i + m])) % q
hash_t = (hash_t + q) % q # 确保hash_t为非负数
- 如果遍历结束仍未找到匹配,返回 - 1。
KMP 模式匹配算法
算法原理
KMP 算法(Knuth-Morris-Pratt Algorithm)由 Donald Knuth、Vaughan Pratt 和 James H. Morris 共同发明,它能够快速地在一个文本串中查找一个模式串,特别是在模式串较长或在一个文本串中多次查找时表现出色。KMP 算法的核心思想是利用已经匹配过的信息,避免不必要的回溯,从而提高匹配效率。
KMP 算法与 BF 算法的 “开局” 是一样的,同样是把主文本和模式串的首位对齐,从左到右逐个字符进行比较。当遇到不匹配的字符时,KMP 算法不是将主文本指针回溯,而是通过一个部分匹配表(也称为失败函数或 next 数组)来确定模式串应该滑动多远,从而避免了主文本指针的回溯。
部分匹配表(next 数组)
KMP 算法的关键在于构建部分匹配表(next 数组)。next 数组是一个一维整型数组,数组的下标代表了 “已匹配前缀的下一个位置”,元素的值则是 “最长可匹配前缀子串的下一个位置”。
构建 next 数组的步骤如下:
- 初始化 next 数组,next [0] = -1,j = -1,i = 0。
- 当 i < m(m 为模式串长度)时:
a. 如果 j == -1 或模式串 [i] == 模式串 [j],则 i++,j++,next [i] = j。
b. 否则,j = next [j]。
next 数组的意义在于:当模式串在位置 i 处与主文本不匹配时,模式串应该回退到 next [i] 的位置继续匹配,而不是从头开始。
举个例子:模式串 “ABCDABD” 的 next 数组计算过程:
模式串索引 | 字符 | 前 i 个字符的子串 | 最长公共前后缀 | next[i] |
---|---|---|---|---|
0 | A | 无(长度 0) | 无 | -1 |
1 | B | “A” | 无 | 0 |
2 | C | “AB” | 无 | 0 |
3 | D | “ABC” | 无 | 0 |
4 | A | “ABCD” | 无 | 0 |
5 | B | “ABCDA” | 前缀 “A” 和后缀 “A” → 长度 1 | 1 |
6 | D | “ABCDAB” | 前缀 “AB” 和后缀 “AB” → 长度 2 | 2 |
主文本:A B C A B C D A B A B C D A B C D A B D E
模式串:A B C D A B D(next 数组:[-1,0,0,0,0,1,2])
具体过程:
- 前 4 位:AA→BB→CC→DD,i=4,j=4。
- 第 5 位:主文本是A,模式串j=4是A→匹配,i=5,j=5。
- 第 6 位:主文本是B,模式串j=5是B→匹配,i=6,j=6。
- 第 7 位:主文本是C,模式串j=6是D→不匹配!
- → 此时查next[6]=2,所以j=2(模式串回退到索引 2 的C)。
- 现在i=7(主文本没动),j=2:主文本C vs 模式串C→匹配,i=8,j=3。
- 继续往后比对,直到j等于模式串长度(7),说明找到匹配,返回i-j(起始位置)。
算法流程
KMP 算法的具体步骤如下:
- 预处理模式串,构建 next 数组。
- 初始化主文本指针 i = 0,模式串指针 j = 0。
- 当 i < n(n 为主文本长度)时:
a. 如果 j == -1 或主文本 [i] == 模式串 [j],则 i++,j++。
b. 否则,j = next [j]。 - 如果 j == m,说明找到匹配,返回 i - j 作为起始位置。
- 如果遍历结束仍未找到匹配,返回 - 1。
BM 模式匹配算法
算法原理
BM 算法(Boyer-Moore Algorithm)由 Robert S. Boyer 和 J Strother Moore 发明,它采用从主文本的尾部开始匹配的策略,并在不匹配时利用两个启发式规则(坏字符规则和好后缀规则)来移动模式串,从而提高匹配效率。
与 BF 和 KMP 算法从左到右进行比较不同,BM 算法采用从右向左比较的方法,这是其效率高的关键原因之一。BM 算法的基本思想是:当发现某个字符不匹配时,利用坏字符规则和好后缀规则来决定模式串向右移动的距离,尽可能多地跳过那些肯定不会匹配的情况。
坏字符规则
坏字符规则(Bad Character Rule)是 BM 算法的第一个启发式规则。当从右向左比较时,发现某个字符无法匹配,这个不匹配的字符称为坏字符(主文本中的字符)。
坏字符规则的处理步骤如下:
- 计算坏字符在模式串中的位置(最右出现位置)。
- 如果坏字符在模式串中不存在,则将模式串向右移动整个长度。
- 如果坏字符存在,则将模式串向右移动,使坏字符的位置与主文本中的坏字符对齐。
数学公式表示:设 Skip (x) 为模式串右移的距离,m 为模式串长度,max (x) 为字符 x 在模式串中最右位置,则:
Skip(x) = {m, 当x不在模式串中时m - 1 - max(x), 当x在模式串中时
}
好后缀规则
好后缀规则(Good Suffix Rule)是 BM 算法的第二个启发式规则。当发现某个字符不匹配时,已经匹配的后缀部分称为好后缀。
好后缀规则的处理步骤如下:
- 如果在模式串中存在另一个与好后缀相同的子串,则将模式串向右移动,使这两个子串对齐。
- 如果在模式串中找不到与好后缀相同的子串,则找到与好后缀的后缀子串相同的模式串最长前缀,将模式串移动到相应位置。
数学公式表示:设 Shift (j) 为模式串右移的距离,m 为模式串长度,j 为当前匹配的字符位置,s 为移动距离,则:
Shift(j) = min{s | (P[j+1..m] = P[j-s+1..m-s]) && (P[j] ≠ P[j-s]) (j > s),P[s+1..m] = P[1..m] (j ≤ s)
}
算法流程
BM 算法的具体步骤如下:
- 预处理模式串,构建坏字符表和好后缀表。
- 初始化主文本指针 i = m-1(模式串长度减一)。
- 进入循环,当 i < n(n 为主文本长度)时:
a. 从模式串的末尾开始,从右向左比较主文本和模式串的字符。
b. 如果所有字符都匹配,返回 i - m + 1 作为起始位置。
c. 如果发现不匹配,计算坏字符规则的移动距离 skip_bad 和好后缀规则的移动距离 skip_good。
d. 将模式串向右移动 max (skip_bad, skip_good) 位,即 i += max (skip_bad, skip_good)。
- 如果循环结束仍未找到匹配,返回 - 1。
AC 自动机多模式匹配算法
算法原理
AC 自动机(Aho-Corasick Algorithm)是一种高效的多模式匹配算法,由 Alfred V. Aho 和 Margaret J. Corasick 发明。AC 自动机基于 Trie 树(前缀树)和 KMP 算法的失配指针(失败指针),能够在一次扫描中同时匹配多个模式串,特别适合处理大量模式串的匹配问题。
AC 自动机的核心思想是:
- 将所有模式串构建成一个 Trie 树结构,共享公共前缀以节省空间。
- 为 Trie 树的每个节点构建失配指针,类似于 KMP 算法的 next 数组,用于在匹配失败时进行回退。
- 通过一次遍历主文本,同时匹配所有模式串,利用失配指针高效地处理不匹配的情况。
Trie 树构建
AC 自动机的第一步是构建 Trie 树(前缀树)来存储所有模式串。构建过程如下:
- 创建根节点。
- 对于每个模式串,从根节点开始,逐个字符插入到 Trie 树中。
- 相同的前缀共享节点,不同的分支表示不同的字符路径。
- 在每个模式串的结尾节点记录该模式串的信息(如模式串的索引或内容)。
例如,对于模式集合 {“he”, “she”, “his”, “hers”},构建的 Trie 树如下:
根节点
├─ h
│ ├─ e (标记"he")
│ └─ i
│ └─ s (标记"his")
└─ s└─ h├─ e (标记"she")└─ r└─ s (标记"hers")
失配指针构建
AC 自动机的第二步是构建失配指针(失败指针),类似于 KMP 算法的 next 数组,但更为复杂,因为需要处理多模式的情况。构建过程如下:
- 使用广度优先搜索(BFS)遍历 Trie 树。
- 根节点的失配指针指向自身。
- 对于每个节点的子节点,找到其父节点的失配指针,然后沿着该失配指针向上查找,直到找到一个节点有对应的子节点,或者回到根节点。
- 将当前子节点的失配指针指向找到的节点。
- 同时,将失配指针路径上的所有输出模式串合并到当前节点,以便在匹配失败时能够找到所有可能的匹配模式。
例如,对于上述 Trie 树,各节点的失配指针如下:
- h 节点的失配指针指向根节点。
- he 节点的失配指针指向根节点。
- his 节点的失配指针指向 s 节点。
- she 节点的失配指针指向 he 节点。
- hers 节点的失配指针指向 his 节点。
模式匹配过程
AC 自动机的第三步是对主文本进行匹配,过程如下:
- 初始化当前节点为根节点。
- 遍历主文本的每个字符:
a. 沿着当前节点的子节点查找与当前字符匹配的节点。
b. 如果找到匹配节点,移动到该节点。
c. 如果未找到匹配节点,沿着失配指针回溯,直到找到匹配节点或回到根节点。
d. 检查当前节点及其失配路径上的所有输出模式串,记录匹配结果。
- 遍历结束后,返回所有找到的匹配位置。
Wu-Manber 多模式匹配算法
算法原理
Wu-Manber 算法是一种高效的多模式匹配算法,由吴升(Sun Wu)和 Udi Manber 发明。Wu-Manber 算法基于 BM 算法的思想,采用哈希的方式以及 BM 算法中的坏字符规则达到快速检索、跳跃式步进的效果。Wu-Manber 算法特别适合处理大量模式串的匹配问题,在许多情况下性能优于 AC 自动机。
Wu-Manber 算法的核心思想是:
- 预处理所有模式串,构建三个关键表:SHIFT 表、HASH 表和 PREFIX 表。
- 使用块字符(多个连续字符)作为比较单位,而不是单个字符,提高匹配效率。
- 在扫描主文本时,根据当前块字符的哈希值查找 SHIFT 表,决定模式串的移动距离,尽可能多地跳过不可能匹配的位置。
- 当需要验证可能的匹配时,使用 HASH 表和 PREFIX 表快速缩小候选模式串集合,减少不必要的比较。
预处理阶段
Wu-Manber 算法的预处理阶段主要构建三个表:
1. SHIFT 表:
- SHIFT 表用于记录每个可能的字符块在模式串中出现的最右位置,从而确定当该字符块出现在主文本中时,模式串应该移动的距离。
- 构建步骤:
a. 确定所有模式串中最短模式串的长度 m。
b. 选择块长度 B(通常取 2 或 3),根据字符集大小和模式串数量确定。
c. 对每个模式串的前 m 个字符,提取所有长度为 B 的子串。
d. 对每个子串 Bl,计算其在模式串中出现的最右位置 j,设置 SHIFT [Bl] = m - j。
e. 对于未出现在任何模式串中的块,设置 SHIFT [Bl] = m - B + 1。
2. HASH 表:
- HASH 表用于存储每个块 Bl 对应的模式串列表,这些模式串的末尾 B 个字符等于 Bl。
- 构建步骤:
a. 对每个模式串,提取其最后 B 个字符作为块 Bl。
b. 计算 Bl 的哈希值 h,将该模式串添加到 HASH [h] 的列表中。
c. 对 HASH 表进行排序,以便快速查找。
3. PREFIX 表:
- PREFIX 表用于存储每个模式串前 B’ 个字符(通常 B’=2)的哈希值,用于进一步缩小候选模式串集合。
- 构建步骤:
a. 对每个模式串,提取其前 B’ 个字符作为前缀。
b. 计算前缀的哈希值,存储在 PREFIX 表中,与对应的模式串关联。
匹配过程
Wu-Manber 算法的匹配过程如下:
- 初始化主文本指针 i = 0。
- 当 i ≤ n - m 时(n 为主文本长度,m 为最短模式串长度):
a. 提取主文本中位置 i 到 i+B-1 的块 Bl。
b. 计算 Bl 的哈希值 h。
c. 查找 SHIFT [h]:
如果 SHIFT [h] > 0,将 i 增加 SHIFT [h]。如果 SHIFT [h] = 0,执行以下步骤:
i. 提取主文本中位置 i 到 i+m-1 的子串 T。ii. 计算 T 的前 B' 个字符的哈希值 hp。iii. 查找 HASH [h] 中的所有模式串,检查其 PREFIX 值是否等于 hp。iv. 对匹配 PREFIX 的模式串,逐个与 T 进行完整比较,记录匹配结果。v. 将 i 增加 1(因为 SHIFT [h]=0 时,必须逐个检查每个可能的起始位置)。
- 返回所有找到的匹配位置。
算法比较与总结
下表总结了六种模式匹配算法的时间复杂度、空间复杂度和适用场景:
算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度 | 适用场景 |
---|---|---|---|---|
BF | O(n+m) | O(n×m) | O(1) | 小规模文本,简单场景 |
RK | O(n+m) | O(n×m) | O(1) | 中等规模文本,哈希冲突少 |
KMP | O(n+m) | O(n+m) | O(m) | 长文本,单模式匹配 |
BM | O(n) | O(n×m) | O(s+m) | 长文本,单模式匹配 |
AC 自动机 | O(n+z) | O(n×m) | O(k) | 多模式匹配,固定模式集合 |
Wu-Manber | O(n+z) | O(n×m) | O(k+s^B) | 多模式匹配,动态模式集合 |
算法设计思想比较
从设计思想上看,这六种算法代表了不同的优化策略:
- 暴力匹配: BF 算法是最基础的暴力匹配方法,没有任何优化,直接逐个字符比较。RK 算法通过哈希技术优化了比较过程,但本质上仍属于暴力匹配的范畴。
- 避免回溯: KMP 算法通过部分匹配表(next 数组)避免了主文本指针的回溯,从而提高了效率。BM 算法则通过从右向左比较和坏字符规则,减少了不必要的比较次数。
- 多模式匹配: AC 自动机和 Wu-Manber 算法专门针对多模式匹配问题设计。AC 自动机基于 Trie 树和失配指针,能够在一次扫描中匹配多个模式串;Wu-Manber 算法则基于 BM 算法的思想,使用哈希表和块字符技术提高匹配效率。
- 空间换时间: RK 算法使用哈希值比较代替字符比较,KMP 算法使用 next 数组存储预处理信息,AC 自动机使用 Trie 树存储模式串,Wu-Manber 算法使用三个预处理表,都是以空间换取时间效率的优化策略。
实际应用中的选择建议
在实际应用中,选择合适的模式匹配算法应考虑以下因素:
- 问题类型: 是单模式匹配还是多模式匹配?多模式匹配应优先考虑 AC 自动机或 Wu-Manber 算法。
- 数据规模: 主文本和模式串的长度如何?对于长文本和长模式串,KMP、BM、AC 自动机和 Wu-Manber 算法更高效。
- 模式串数量: 如果有大量模式串,AC 自动机或 Wu-Manber 算法是更好的选择。
- 动态变化: 模式串集合是否会动态变化?Wu-Manber 算法在动态更新模式串时更具优势。
- 实现复杂度: 算法的实现难度如何?BF 和 RK 算法实现简单,适合快速开发;KMP、BM、AC 自动机和 Wu-Manber 算法实现较为复杂。
- 环境限制: 是否有内存限制?BF、RK 和 KMP 算法内存需求较小,适合内存受限的环境;AC 自动机和 Wu-Manber 算法可能需要较多内存。