目录
前言
写本篇文章的动机源自于备战考研时,学习到《4.3.3 串的模式匹配算法》中的KMP算法时遇到瓶颈卡住了,在查阅其他教案和文档后终于理解,于是将自己的见解整理出来供大家参考,希望能够帮助大家学习进步。本文参考教材:《数据结构(C语言版)第二版》-严蔚敏
第一章 BF算法
1.1 BF算法步骤
BF算法详细教案大家可以参考《数据结构(C语言版)第二版》中P89—P90中的内容,这里我就不做抄录了,本篇文章只做重点示意和难点解析。
【算法步骤】
①分别使用计数指针 i 和 j 指向主串 S 和字串 T。
②若主串 S 和字串 T均未比较至串尾部(即:主串 S 和字串 T 未匹配完成,i 和 j 的值分别小于S.length 和 T.length)则执行循环操作。
将主串 S 和字串 T中的指针 i 和 j 所指向字符的值作比较:
若相等:则 i 和 j 分别后移一位,继续比较后续字符,直到比较到队尾结束。
若不相等:则子串 T 的指针 j 返回至子串 T 起始字符位置(j = 1),并从主串 S 上一次匹配的起始字符位置的下一位重新匹配(i = i - j + 2)。
③若 j > T.length 则说明,字串 T 的字符已全部对比并于主串 S 匹配完毕。
1.2 算法解析
【算法解析】
以《数据结构(C语言版) 第二版》教材P89-图4.4为例:
↓ i = 3
第一趟匹配 <主串S> a b a b c a b c
<子串T> a b c
↑ j = 3
↓ i = 3
第二趟匹配 <主串S> a b a b c a b c
<子串T> a
↑ j = 1
由图可知:在第一趟匹配中主串的指针 i = 3所在字符 与子串指针 j = 3 失配后,第二趟匹配字串直接从主串指针 i = 2 处开始匹配,那么引出问题主串的下一匹配字符公式(i = i - j + 2)如何推导?
以第一趟匹配为例:
① 当 i = 3 和 j = 3 匹配不成功时,意为 i = 3 和 j = 3 之前的字符全部匹配,所以先返回至匹配开始的位置,即主串 i = 1 处(标绿色的a),用 i = i - j + 1 可以计算出匹配起始位置,j 代表从主串匹配起始位置经过的距离(经过距离为 j = 3 ,子串已执行匹配的字符数量),因为主串和子串指针坐标从数字 1 开始,所以对 i - j 的结果 + 1 得出起始位置。
↓ i = 3 (失配)
第一趟匹配 <主串S> a b a b c a b c
<子串T> a b c
↑ ↑ ↑ j = 3(失配)
j = 1 j = 2
② 对得出的起始位置再 + 1 ,意为从下一个字符开始匹配, i - j + 1 + 1 就是每次匹配失败后,都从是一次匹配开始的下一个字符开始重新匹配。
1.3 总结
BF 算法核心就是沿着主串的初次匹配的字符开始,匹配失败后回到原点,再从下一个字符开始匹配。
第二章 KMP 算法
2.1 KMP 算法解析
KMP 算法过程较为复杂,大家可以直接参考《数据结构(C语言版)第二版》中P90—P94中的内容,这里直接对算法进行解析。
【算法解析】
① 首先,我们要引入一个概念:“最长相等前后缀”。我们以教材P91(图 4.6)为例,模式串 t = {a b a a b c a c},截取部分{a b a a b},其前缀和后缀如下:
模式串 t = {a b a a b}
前缀 {a , ab , aba , abaa} 前缀排除最后一位。
后缀 {b , ab , aab , baab} 后缀排除第一位。
由此可见在图中所示的前缀和后缀集合中,只有{ab}为两者共有。
此时可知“最长相等前后缀”为{ab},长度值为“2”。
②理解“最长相等前后缀”的概念后,我们通过书中讲解可知,KMP 算法关键在于“主串 i 与子串 j 失配时,主串 i 不动,子串回溯从第 k 个元素开始和主串 i 匹配”,因此当出现失配时,子串从哪个位置的字符与主串 i 匹配就需要引入 NEXT 函数计算。
③这里我先给出 NEXT【j】函数的计算方法,之后再给出解析:
j 下标值 : 1 2 3 4 5 6 7 8
子串 S : a b a a b c a c
NEXT [ j ] : 0 1 1 2 2 3 1 2
NEXT [ j ] 的值为,第 j 前的(j - 1)个子串中,最长相等前后缀长度 + 1,当 j = 4 时,前(j - 1)个子串为{a b a},可见“最长相等前后缀”为{a},则NEXT 【j】的值为 1 + 1 = 2。表示当 j 和 i 不匹配时,子串 S 从下标 2 开始和主串 i 位置开始比较。
④这里需要弄清楚两个问题:
1.“最长相等前后缀长度”的作用。
2.为何NEXT 【j】的值为:“最长相等前后缀长度” + 1。
i : 1 2 3 4 5 6 7 8
主串:a c a b a a b a a a b c
子串: a b a a b c
j : 1 2 3 4 5 6
如上图所示,主串 i = 8 和子串 j = 6 (失配),我们可知主串的 3 至 7 和 子串的 1 至 5 是匹配的,同时通过NEXT 【j】计算子串的值,NEXT【6】 = 3,说明当 子串 j = 6 时失配后,子串要从下标 j = 3 处与 i 匹配,此时 j = 6之前的子串中“最长相等前后缀”为{ab}。
主串 : a b a a b a
子串 : a b a a b a
“最长相等前后缀” 可以理解为“前后重复长度”
由于这一段{a b a a b}与主串匹配,且前后存在重复项,因此可以直接从“重复部分”的下一位开始比较。
总结:
1.“最长相等前后缀长度”的作用:找出主串和子串已匹配部分是否存在“重复部分”,再次匹配时直接从“重复部分”开始比较,节省时间。
2.为何NEXT 【j】的值为:“最长相等前后缀长度” + 1:j 的下标从1开始计算,所以计算结果要+ 1。
3.KMP 算法核心思想:当匹配过程中产生“失配”,指针 i 不变,指针 j 回溯到 NEXT【j】的值所示位置上重新匹配,当 i 和 NEXT【1】的值“失配”时,则主串和子串下标各 + 1,说明子串第一个字符和 i 所值字符不等 。
2.2 NEXT 函数修订值
详见教材p94内容,此处要点为当子串和主串中存在大量相同的连续字符时,可以跳过相同字符,节省时间。