Knuth-Morris-Pratt算法
的公里的算法是由Donald Knuth, Vaughan Pratt和James H. Morris提出的一种高效的字符串匹配算法。这是一种线性时间算法,它利用这样一种观察,即每当发生匹配(或不匹配)时,模式本身包含足够的信息来指示新的检查应该从哪里开始。
字符串匹配问题
问题:给定一个字符串
年代
和字符串p
短于或等于年代
的所有事件p
在年代
.
字符串匹配问题可能与许多情况相关,包括但不限于在文本编辑器中使用搜索功能,为搜索引擎建立数据库或处理基因组序列。
可以看到,算法在实践中经常遇到最坏的情况(没有模式出现),需要仔细设计。
最简单的方法是滑动一个窗口大小 到处都是 检查是否匹配。
1 2 3 4 5 6 7 8 9 |
|
这很有效,但实际上效率很低。这需要 或者更紧密 时间。
前缀函数
每当naive函数失败(或成功)时,它就开始从下一个字符开始匹配。这可能没有必要。我们可以使用上次匹配点的知识和测试字符串的结构来知道下一次匹配应该从哪里开始。
字符串的上述“结构”封装在前缀的函数(表示 )的测试字符串。这是对字符串与自身移位匹配程度的描述。
给定一个模式 的长度 ,函数 地图 来 这样 的最长前缀的长度是多少 这是一个合适的后缀
找到匹配
现在,我们准备开始在字符串中寻找模式的出现。
假设我们匹配了目标字符串中模式的几个字符,并发现不匹配。我们是否应该从前面匹配的下一个字母开始?也许不是。我们可以在前缀函数中使用预先计算的知识,它会告诉我们新的匹配是否可能包含先前匹配的字母。
让我们试着匹配这个模式
ATAAT
反对GATCCATATG
.
我们尝试匹配
一个
反对G
这是否定的。所以,我们继续下一个字符。我们匹配
在
在反模式中在
在字符串中。然后我们遇到C
并尝试与之匹配T
没有成功的模式。我们忽略了试图匹配
T
紧接着一个
因为前缀函数告诉我们 .换句话说,匹配不可能从那个位置开始,因为有一个T
这与图案的开头不一致。没有什么有趣的事情发生
ATA
是匹配的。下一个字符一个
在图案上不一致T
在字符串中。下一个匹配应该考虑到我们已经匹配了
在
接近前一个匹配的结束,不需要再次匹配。我们只需要检查下面的字母在
在模式匹配。
这里所描述的想法需要一些时间才能理解。这个视频可能会有帮助。
复杂性
时间复杂度
因为prefix-函数已经告诉我们需要对已经匹配的部分做什么,我们不需要再次检查这些部分,因此,只遍历字符串一次就足够了。匹配的时间复杂度是 .
预计算呢?事实证明,如果正确实施,它可以在 时间。
总之,它需要 时间。但自 ,时间是有界限的 .这当然比平时好
空间复杂度
我们只需要和图案一样大的空间。所以空间要求是 .
注意,在一次线性传递中一个字符一个字符地读取实际的字符串在这里是可以的。
实现
前缀的函数
12 3 4 5 6 7 8 9 10 11 12 13 |
|
匹配
12 3 4 5 6 7 8 9 10 11 12 13 14 |
|