搜索模式| 系列2——KMP算法
给定一个文本txt [0..n-1]和一个模式pat [0..m-1],写一个搜索函数search(char pat [],char txt []),在txt中打印所有出现的pat [] []。可以假设n> m。
例子:
Input: txt[] = "THIS IS A TEST TEXT" pat[] = "TEST" Output: Pattern found at index 10 Input: txt[] = "AABAACAADAABAABA" pat[] = "AABA" Output: Pattern found at index 0 Pattern found at index 9 Pattern found at index 12
模式搜索是计算机科学中的一个重要问题。当我们在记事本/ word文件或浏览器或数据库中搜索字符串时,使用模式搜索算法来显示搜索结果。
我们在前面的文章中已经讨论过Naive模式搜索算法。Naive算法的最坏情况复杂度是O(m(n-m + 1))。在最坏的情况下,KMP算法的时间复杂度是O(n)。
KMP(克努特莫里斯普拉特)模式搜索的Naive模式搜索算法并不在在这种情况下很好的工作:许多匹配字符,随后是不匹配的字符的情况下。以下是一些例子。
txt[] = "AAAAAAAAAAAAAAAAAB" pat[] = "AAAAB" txt[] = "ABABABCABABABCABABABC" pat[] = "ABABAC" (not a worst case, but a bad case for Naive)
KMP匹配算法使用该模式的退化性质(在模式中具有相同子模式出现不止一次的模式),并将最坏情况复杂度提高到O(n)。
KMP算法背后的基本思想是:每当我们检测到一个不匹配(在一些匹配之后),我们已经知道下一个窗口文本中的一些字符。我们利用这些信息避免匹配我们知道无论如何将匹配的字符。让我们考虑下面的例子来理解这一点。
Matching Overview txt = "AAAAABAAABA" pat = "AAAA" 我们对比txt和pat: txt = "AAAAABAAABA" pat = "AAAA" 初始位置 我们找到了一个和原始字符串相同的匹配。
下一步,我们比较txt中接下来的字符串和pat字符串 txt = "AAAAABAAABA" pat = "AAAA" [初始位置的下一个位置]
这就是KMP算法优于基本模式匹配算法的地方了。在第二次比较中,我们只比较了pattern的前4个'A',然后我们决定当前是否匹配,我们已经知道前三个匹配,我们直接跳过前三个即可。
需要预处理吗?
上述解释产生了一个重要问题,如何知道要跳过多少字符。要知道为此,我们预处理模式并准备一个整数数组。告诉我们要跳过的字符数。
预处理概述:
- KMP算法对pat []进行预处理,并构造一个大小为m(与模式大小相同)的辅助lps [],用于匹配时跳过字符。
- 名称lps表示最长的正确前缀,也是后缀。。一个适当的前缀是前缀与整个字符串不容许。例如,“ABC”的前缀是“”,“A”,“AB”和“ABC”。正确的前缀是“”,“A”和“AB”。字符串的后缀是“”,“C”,“BC”和“ABC”。
- 对于其中i = 0到m-1的每个子模式pat [0..i],lps [i]存储最大匹配正确前缀的长度,其也是子模式pat [0..i]的后缀。
lps[i] = the longest proper prefix of pat[..i] which ..i].
注意: lps [i]也可以定义为最长的前缀,也是正确的后缀。我们需要在一个地方使用正确的,以确保整个子字符串不被考虑。
Examples of lps[] construction: For the pattern “AAAA”, lps[] , , , ] For the pattern “ABCDE”, lps[] , , , , ] For the pattern “AABAACAABAA”, lps[] , , , , , , , , , , ] For the pattern “AAACAAAAAC”, lps[] , , , , , , , , , ] For the pattern “AAABAAA”, lps[] , , , , , , ]
搜索算法:
与Naive算法不同的是,我们将模式依次匹配一个,然后比较每次匹配中中的所有字符,我们使用来自lps []的值来决定下一个要匹配的字符。这个想法是不匹配我们知道无论如何将匹配的字符。
如何使用lps []来决定下一个职位(或知道要跳过的字符数)?
- 我们开始与j = 0的pat [j]与当前文本窗口的字符进行比较。
- 我们保持匹配字符txt [i]和pat [j],并在pat [j]和txt [i]保持匹配的同时不断增加i和j 。
- 当我们看到不匹配的时候
- 我们知道,字符pat [0..j-1]与txt [i-j + 1 ... i-1]匹配(注意,j从0开始并且只有在匹配时才增加)。
- 我们也知道(从上面的定义中)lps [j-1]是pat [0 ... j-1]的字符数,它们都是正确的前缀和后缀。
- 从上面两点我们可以得出结论,我们不需要将这些lps [j-1]字符与txt [ij ... i-1]进行匹配,因为我们知道这些字符无论如何将匹配。让我们考虑上面的例子来理解这一点。
txt[] = "AAAAABAAABA" pat[] = "AAAA" lps[] = {0, 1, 2, 3} i = 0, j = 0 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j[ match, do i++, j++ i = 1, j = 1 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j[ match, do i++, j++ i = 2, j = 2 txt[] = "AAAAABAAABA" pat[] = "AAAA" pat[i] and pat[j[ match, do i++, j++ i = 3, j = 3 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j[ match, do i++, j++ i = 4, j = 4 Since j == M, print pattern found and resset j, j = lps[j-1] = lps[3] = 3 Here unlike Naive algorithm, we do not match first three characters of this window. Value of lps[j-1] (in above step) gave us index of next character to match. i = 4, j = 3 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j[ match, do i++, j++ i = 5, j = 4 Since j == M, print pattern found and reset j, j = lps[j-1] = lps[3] = 3 Again unlike Naive algorithm, we do not match first three characters of this window. Value of lps[j-1] (in above step) gave us index of next character to match. i = 5, j = 3 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] do NOT match and j > 0, change only j j = lps[j-1] = lps[2] = 2 i = 5, j = 2 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] do NOT match and j > 0, change only j j = lps[j-1] = lps[1] = 1 i = 5, j = 1 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] do NOT match and j > 0, change only j j = lps[j-1] = lps[0] = 0 i = 5, j = 0 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] do NOT match and j is 0, we do i++. i = 6, j = 0 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] match, do i++ and j++ i = 7, j = 1 txt[] = "AAAAABAAABA" pat[] = "AAAA" txt[i] and pat[j] match, do i++ and j++
下面是代码实现:
// C++ program for implementation of KMP pattern searching // algorithm #include<bits/stdc++.h> void computeLPSArray(char *pat, int M, int *lps); // Prints occurrences of txt[] in pat[] void KMPSearch(char *pat, char *txt) { int M = strlen(pat); int N = strlen(txt); // create lps[] that will hold the longest prefix suffix // values for pattern int lps[M]; // Preprocess the pattern (calculate lps[] array) computeLPSArray(pat, M, lps); ; // index for txt[] ; // index for pat[] while (i < N) { if (pat[j] == txt[i]) { j++; i++; } if (j == M) { printf("Found pattern at index %d n", i-j); j = lps[j-]; } // mismatch after j matches else if (i < N && pat[j] != txt[i]) { // Do not match lps[0..lps[j-1]] characters, // they will match anyway ) j = lps[j-]; else i = i+; } } } // Fills lps[] for given patttern pat[0..M-1] void computeLPSArray(char *pat, int M, int *lps) { // length of the previous longest prefix suffix ; lps[] = ; // lps[0] is always 0 // the loop calculates lps[i] for i = 1 to M-1 ; while (i < M) { if (pat[i] == pat[len]) { len++; lps[i] = len; i++; } else // (pat[i] != pat[len]) { // This is tricky. Consider the example. // AAACAAAA and i = 7. The idea is similar // to search step. ) { len = lps[len-]; // Also, note that we do not increment // i here } else // if (len == 0) { lps[i] = ; i++; } } } } // Driver program to test above function int main() { char *txt = "ABABDABACDABABCABAB"; char *pat = "ABABCABAB"; KMPSearch(pat, txt); ; }
输出:
Found pattern at index
预处理算法:
在预处理部分,我们计算lps []中的值。为此,我们跟踪前一个索引的最长前缀后缀值(我们使用len变量用于此目的)的长度。我们将lps [0]和len初始化为0.如果pat [len]和pat [i]匹配,我们将len加1,并将增加的值赋给lps [i]。如果pat [i]和pat [len]不匹配,len不为0,我们将len更新为lps [len-1]。有关详细信息,请参阅下面的代码中的computeLPSArray()。
预处理的插图(或构建lps [])
pat[] = "AAACAAAA" len = , i = . lps[] , we move to i = len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = len = , i = . Since pat[len] and pat[i] , ] = lps[] = len = , i = . Since pat[len] and pat[i] , len = lps[len-] = lps[] = len = , i = . Since pat[len] and pat[i] , Set lps[] = and i = . len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = len = , i = . Since pat[len] and pat[i] , ] = lps[] = len = , i = . Since pat[len] and pat[i] match, do len++, store it in lps[i] and do i++. len = , lps[] = , i = We stop here as we have constructed the whole lps[].
ok,如果有问题,随时提问
搜索模式| 系列2——KMP算法的更多相关文章
- 模式串匹配之KMP算法
模式串匹配之KMP算法 KMP算法 模式值计算(next[j]) (1) next[0]=-1, 第一个字符模式值为-1 (2) next[j]=-1, T中下标为j的字符与首字符相同,且j前面的1 ...
- 讲不明白自杀系列:KMP算法
算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...
- KMP算法的Next数组详解
转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/32 ...
- 【转】KMP算法
转载请注明来源,并包含相关链接.http://www.cnblogs.com/yjiyjige/p/3263858.html 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初 ...
- KMP算法的Next数组详解 转
这个写的很好,还有讲kmp,值得一看. http://www.cnblogs.com/tangzhengyue/p/4315393.html 转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法 ...
- KMP算法的Next数组详解(转)
转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧: http://www.cnblogs.com/yjiyjige/p/3 ...
- (转)KMP算法实现。超级赞!见过的最容易理解的
网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/3263858.html这位同学用详细 ...
- 串的模式匹配和KMP算法
在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,KMP算法就是一个高效的模式匹配算法.KMP算法是蛮力算法的一种改进,下面我们先来介绍蛮 ...
- 模式匹配的KMP算法详解
这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法.大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白. 注意到这 ...
随机推荐
- Java面向对象回顾(1)
世界万物皆对象. 面向对象四大特性:继承.封装.多态.抽象 Java中现有类,再有对象.创建对象(对象实例化)必须先创建类. 将对象的特征对应写成类的属性. 将对象的方法对应携程类的方法. 如何创建对 ...
- nginx反向代理node.js获取客户端IP
使用Nginx做node.js程序的反向代理,会有这么一个问题:在程序中获取的客户端IP永远是127.0.0.1 如果想要拿到真实的客户端IP改怎么办呢? 一.首先配置Nginx的反向代理 proxy ...
- 爬起点小说day03
# 把所有类别的前3页的小说爬取下来 import scrapyfrom scrapy.http import Requestfrom time import sleepfrom qidianNove ...
- 揭秘 HashMap 实现原理(Java 8)
HashMap 作为一种容器类型,无论你是否了解过其内部的实现原理,它的大名已经频频出现在各种互联网面试中了.从基本的使用角度来说,它很简单,但从其内部的实现来看(尤其是 Java 8 的改进以来), ...
- Windows Intellij环境下Gradle的 “Could not determine Java version from ‘9.0.1’”的解决方式
当我导入Gradle项目初试Java spring的时候,遇到下面报错: Gradle complete project refresh failed Error:Could not determin ...
- Linux常用命令及部分详解
1.总结部分 常用指令 ls 显示文件或目录 -l 列出文件详细信息l(list) -a 列出当前目录下所有文件及目录,包括隐藏的a(all) m ...
- HDU5804--Price List
Price List Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/131072 K (Java/Others) Tot ...
- 源码安装pipelineDB之CentOS7
源码下载:https://github.com/pipelinedb/pipelinedb github上面README只要是针对ubunte来安装的. 在正式安装前,要先下载好依赖的包: check ...
- jq,返回上一页,小记history.back(-1)和history.go(-1)区别
<input type="button" name="back" value="重新填写" onclick="javascr ...
- dom4j详解
Dom4j下载及使用Dom4j读写XML简介要使用dom4j读写XML文档,需要先下载dom4j包,dom4j官方网站在 http://www.dom4j.org/目前最新dom4j包下载地址:htt ...