搜索模式| 系列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写一个通用的服务器程序》01 综述
最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...
- 微信小程序与Java后台的通信
一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很明白小程序如何与后台进行通信的, ...
- Shell编程基础篇
1.1 前言 1.1.1 为什么学Shell Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具, Linux/UNIX系统的底层及基础应用软件的核心大都涉及Shell脚 ...
- ChatterBot之使用mongodb 03
上一篇我们已经搭建好了mongodb环境,本篇为简单示例. 废话不多说先上代码然后开始讲解; !!!别忘了打开你的mongdb服务!!!,如果没有mongodb请看上篇如何安装mongodb; # - ...
- 【java提高】---HashMap解析(一)
HashMap解析(一) 平时一直再用hashmap并没有稍微深入的去了解它,自己花点时间想往里面在深入一点,发现它比arraylist难理解很多,好多东西目前还不太能理解等以后自己知识更加丰富在过来 ...
- String、StringBuilder和StringBuffer
1.string不可变性 java的docs有这样一句话:Strings are constant; their values cannot be changed after they are cre ...
- HDU-1242-Rescu
Rescue Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Sub ...
- Hibernate框架进阶(上篇)
导读 前面一片文章介绍了Hibernate框架的入门,主要是讲解Hibernate的环境搭建和简单测试,有兴趣的童鞋出门左转.本文在入门的基础上进行Hibernate的进阶讲解,分为上中下三篇,本篇为 ...
- 小白的Python之路 day1
Python之路,Day1 - Python基础1 本节内容 Python介绍 发展史 Python 2 or 3? 一. Python介绍 python的创始人为吉多·范罗苏姆(Guido van ...
- iscroll4 input textarea不能获得焦点问题
最近在做移动端项目的时候,使用iscroll4实现页面滚动效果,之后发现页面中的input,textarea等不能得到焦点,输入内容. 问题原因是: 使用iscroll之后,输入框无法聚焦,页面文字等 ...