模式匹配的KMP算法详解
KMP讲解

有了覆盖函数,那么实现kmp算法就是很简单的了,我们的原则还是从左向右匹配,但是当失配发生时,我们不用把target_index向回移动,target_index前面已经匹配过的部分在pattern自身就能体现出来,只要动pattern_index就可以了。
当发生在j长度失配时,只要把pattern向右移动j-overlay(j)长度就可以了。
说了这么半天那么这种方法是什么呢,这种方法是就大名鼎鼎的确定的有限自动机(Deterministic finite state automaton DFA),DFA可识别的文法是3型文法,又叫正规文法或是正则文法,既然可以识别正则文法,那么识别确定的字串肯定不是问题(确定字串是正则式的一个子集)。对于如何构造DFA,是有一个完整的算法,这里不做介绍了。在识别确定的字串时使用DFA实在是大材小用,DFA可以识别更加通用的正则表达式,而用通用的构建DFA的方法来识别确定的字串,那这个overhead就显得太大了。
{
index = overlay_value[index];
}
if(pattern[index+1]==pattern[i])
{
overlay_value[i] = index +1;
KMP 算法可在O(n+m)时间内完成全部的串的模式匹配工作。
ok,最后给出KMP算法实现的c++代码:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int kmp_find(const string& target,const string& pattern)
kmp的nextval数组

kmp的nextval数组【KMP算法之NextVal数组】KMP算法是一种高效的字符串匹配算法,其核心思想是利用已经匹配的部分字符来跳过不必要的比较,以达到快速匹配的目的。
其中一个重要的优化是使用nextval数组,用于在失配时找到下一次需要比较的位置。
1. KMP算法简介:KMP算法由Knuth、Morris和Pratt三人提出,其核心思想是根据模式串(pattern)自身的特点,预先计算出一个nextval 数组,用于在匹配过程中跳过不必要的比较。
KMP算法的时间复杂度为O(m+n),其中m和n分别为主串(target)和模式串(pattern)的长度。
2. NextVal数组的定义:NextVal数组是在求解next数组时的一种优化,其定义与next 数组略有不同。
NextVal数组的值nextval[i]表示,当模式串的第i个字符与主串的第i个字符失配时,下一个需要比较的位置在哪里。
3. NextVal数组的求解:(1)定义一个辅助数组next[],用于存储当前位置之前的最大相同前后缀长度。
(2)对模式串进行遍历,计算next数组的值:a. 初始化next[0] = -1,next[1] = 0;b. 使用两个指针i和j,分别指向模式串的第i个字符和第j个字符,初始值为i=2,j=0;c. 若p[i-1] == p[j],则next[i] = j + 1,同时i++,j++;d. 若p[i-1] != p[j],则在j = next[j]的基础上,继续比较,直到p[i-1] == p[j]或者j = 0;e. 若j = 0,则next[i] = 0,同时i++;f. 重复步骤c~e,直到遍历完整个模式串。
(3)通过next数组求解nextval数组:a. 初始化nextval[1] = 0;b. 遍历next数组,对于next[i] >= 0的位置,若p[i] ==p[next[i]],则nextval[i+1] = next[i] + 1;c. 若p[i] != p[next[i]],则继续在next数组中找到next[next[i]],直到找到p[i] == p[next[next[i]]]或者next[i] = -1;d. 若找到p[i] == p[next[next[i]]],则nextval[i+1] =next[next[i]] + 1;e. 若next[i] = -1,则nextval[i+1] = 0。
严蔚敏 数据结构 kmp算法详解

当此集合非空时
next[j]= -1 0 当j=0时 其他情况
t=“abab”对应的next数组如下:
j t[j] next[j] 0 a -1 1 b 0 2 a 0 3 b 1
void GetNext(SqString t,int next[]) { int j,k; j=0;k=-1;next[0]=-1; while (j<t.len-1)
既然如此,回溯到si-j+1开始与t匹配可以不做。那 么,回溯到si-j+2 开始与t匹配又怎么样?从上面推理 可知,如果 "t0t1…tj-2"≠"t2t3…tj"
仍然有
"t0t1…tj-2"≠"si-j+2si-j+3…si"
这样的比较仍然“失配”。依此类推,直到对于 某一个值k,使得: "t0t1…tk-2"≠" tj-k+1tj-k+2…tj-1"
b 3
第 1 次匹配
第 2 次匹配
s=aaabaaaa b t=aaaab
第 3 次匹配
s=aaabaaaa b t=aaaab
第 4 次匹配
s=aaabaaaa b t=aaaab
第 5 次匹配
s=aaabaaaa b t=aaaab
上述定义的next[]在某些情况下尚有缺陷。 例如,模式“aaaab”在和主串“aaabaaaab”匹配时, 当i=3,j=3时,s.data[3]≠t.data[3],由next[j]的指示还需 进行i=3、j=2,i=3、j=1,i=3、j=0等三次比较。实际上, 因为模式中的第1、2、3个字符和第4个字符都相等, 因此,不需要再和主串中第4个字符相比较,而可以将模 式一次向右滑动4个字符的位置直接进行i=4,j=0时的 字符比较。
kmp算法python代码

kmp算法python代码摘要:1.KMP 算法简介2.KMP 算法的Python 实现3.KMP 算法的应用示例正文:1.KMP 算法简介KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,用于在一个主字符串中查找一个子字符串出现的位置。
该算法的关键在于通过预处理子字符串,减少不必要的字符比较,从而提高匹配速度。
2.KMP 算法的Python 实现以下是KMP 算法的Python 实现:```pythondef compute_prefix_function(pattern):m = len(pattern)prefix_function = [0] * (m + 1)prefix_function[0] = 0i, j = 1, 0while i < m:if pattern[i] == pattern[j]:j += 1prefix_function[i] = ji += 1else:if j!= 0:j = prefix_function[j - 1]else:prefix_function[i] = 0i += 1return prefix_functiondef kmp_search(text, pattern):m, n = len(text), len(pattern)prefix_function = compute_prefix_function(pattern) i, j = 0, 0while i < m:if pattern[j] == text[i]:i += 1j += 1if j == n:return i - jelif i < m and pattern[j]!= text[i]:if j!= 0:j = prefix_function[j - 1]else:i += 1return -1if __name__ == "__main__":text = "我国是一个伟大的国家"pattern = "伟大的"result = kmp_search(text, pattern)if result!= -1:print("子字符串"{}" 在主字符串中第{} 位置出现。
KMP算法(改进的模式匹配算法)——next函数

KMP算法(改进的模式匹配算法)——next函数KMP算法简介KMP算法是在基础的模式匹配算法的基础上进⾏改进得到的算法,改进之处在于:每当匹配过程中出现相⽐较的字符不相等时,不需要回退主串的字符位置指针,⽽是利⽤已经得到的部分匹配结果将模式串向右“滑动”尽可能远的距离,再继续进⾏⽐较。
在KMP算法中,依据模式串的next函数值实现字串的滑动,本随笔介绍next函数值如何求解。
next[ j ]求解将 j-1 对应的串与next[ j-1 ]对应的串进⾏⽐较,若相等,则next[ j ]=next[ j-1 ]+1;若不相等,则将 j-1 对应的串与next[ next[ j-1 ]]对应的串进⾏⽐较,⼀直重复直到相等,若都不相等则为其他情况题1在字符串的KMP模式匹配算法中,需先求解模式串的函数值,期定义如下式所⽰,j表⽰模式串中字符的序号(从1开始)。
若模式串p 为“abaac”,则其next函数值为()。
解:j=1,由式⼦得出next[1]=0;j=2,由式⼦可知1<k<2,不存在k,所以为其他情况即next[2]=1;j=3,j-1=2 对应的串为b,next[2]=1,对应的串为a,b≠a,那么将与next[next[2]]=0对应的串进⾏⽐较,0没有对应的串,所以为其他情况,也即next[3]=1;j=4,j-1=3 对应的串为a,next[3]=1,对应的串为a,a=a,所以next[4]=next[3]+1=2;j=5,j-1=4 对应的串为a,next[4]=2,对应的串为b,a≠b,那么将与next[next[4]]=1对应的串进⾏⽐较,1对应的串为a,a=a,所以next[5]=next[2]+1=2;综上,next函数值为 01122。
题2在字符串的KMP模式匹配算法中,需先求解模式串的函数值,期定义如下式所⽰,j表⽰模式串中字符的序号(从1开始)。
若模式串p为“tttfttt”,则其next函数值为()。
串的两种模式匹配算法

串的两种模式匹配算法 模式匹配(模范匹配):⼦串在主串中的定位称为模式匹配或串匹配(字符串匹配) 。
模式匹配成功是指在主串S中能够找到模式串T,否则,称模式串T在主串S中不存在。
以下介绍两种常见的模式匹配算法:1. Brute-Force模式匹配算法暴风算法,⼜称暴⼒算法。
算法的核⼼思想如下: 设S为⽬标串,T为模式串,且不妨设: S=“s0s1s2…sn-1” , T=“t0t1t2 …tm-1” 串的匹配实际上是对合法的位置0≦i≦n-m依次将⽬标串中的⼦串s[i…i+m-1]和模式串t[0…m-1]进⾏⽐较:若s[i…i+m-1]=t[0…m-1]:则称从位置i开始的匹配成功,亦称模式t在⽬标s中出现;若s[i…i+m-1]≠t[0…m-1]:从i开始的匹配失败。
位置i称为位移,当s[i…i+m-1]=t[0…m-1]时,i称为有效位移;当s[i…i+m-1] ≠t[0…m-1]时,i称为⽆效位移。
算法实现如下: (笔者偷懒,⽤C#实现,实际上C# String类型已经封装实现了该功能)1public static Int32 IndexOf(String parentStr, String childStr)2 {3 Int32 result = -1;4try5 {6if (parentStr.Length > 1 && childStr.Length > 1)7 {8 Int32 i = 0;9 Int32 j = 0;10while (i < parentStr.Length && j < childStr.Length)11 {12if (parentStr[i] == childStr[j])13 {14 i++;15 j++;16 }17else18 {19 i = i - j + 1;20 j = 0;21 }22 }23if (i < parentStr.Length)24 {25 result = i - j;26 }27 }28 }29catch (Exception)30 {31 result = -1;32 }33return result;34 } 该算法的时间复杂度为O(n*m) ,其中n 、m分别是主串和模式串的长度。
不同的模式匹配方法详解(暴力、KMP、Rabin-Karp算法)

不同的模式匹配⽅法详解(暴⼒、KMP、Rabin-Karp算法)1 概述单模式匹配是处理字符串的经典问题,指在给定字符串中寻找是否含有某⼀给定的字串。
⽐较形象的是CPP中的strStr()函数,Java的String 类下的indexOf()函数都实现了这个功能,本⽂讨论⼏种实现单模式匹配的⽅法,包括暴⼒匹配⽅法、KMP⽅法、以及Rabin-Karp⽅法(虽然Rabin-Karp⽅法在单模式匹配中性能⼀般,单其多模式匹配效率较⾼,且采取⾮直接⽐较的⽅法也值得借鉴)。
算法预处理时间匹配时间暴⼒匹配法O(mn)KMP O(m)O(n)Rabin-Karp O(m)O(mn)2 暴⼒匹配模式匹配类的问题做法都是类似使⽤⼀个匹配的滑动窗⼝,失配时改变移动匹配窗⼝,具体的暴⼒的做法是,两个指针分别指向长串的开始、短串的开始,依次⽐较字符是否相等,当不相等时,指向短串的指针移动,当短串指针已经指向末尾时,完成匹配返回结果。
以leetcode为例给出实现代码(下同)class Solution {public int strStr(String haystack, String needle) {int m = haystack.length(), n = needle.length();if (needle.length() == 0) return 0;for (int i = 0; i <= m - n; i++) {for (int j = 0; j < n; j++) {if (haystack.charAt(i + j) != needle.charAt(j))break;if (j == n - 1)return i;}}return -1;}}值得注意的是,Java中的indexO()⽅法即采⽤了暴⼒匹配⽅法,尽管其算法复杂度⽐起下⾯要谈到的KMP⽅法要⾼上许多。
⼀个可能的解释是,⽇常使⽤此⽅法过程中串的长度都⽐较短,⽽KMP⽅法预处理要⽣成next数组浪费时间。
BF算法KMP算法BM算法

BF算法KMP算法BM算法BF算法(Brute-Force Algorithm)是一种简单直接的字符串匹配算法,也称为朴素算法。
BF算法的基本思想是从主串的第一个字符开始,每次移动一个字符,然后和模式串进行逐个字符比较,如果不匹配,则继续下一个位置的比较。
如果字符匹配,则比较下一个字符,直到找到完全匹配的子串或者主串遍历结束。
BF算法的时间复杂度为O(m*n),其中m和n分别为主串和模式串的长度。
当主串和模式串的长度较小时,BF算法是一个简单高效的字符串匹配算法。
然而,当主串和模式串的长度非常大时,BF算法的效率会非常低下。
KMP算法(Knuth-Morris-Pratt Algorithm)是一种改进的字符串匹配算法。
KMP算法的核心思想是利用已经匹配过的部分信息来避免不必要的字符比较。
KMP算法通过构建一个跳转表(也称为失配函数),记录当前位置之前的字符中可能出现的最大公共前后缀长度。
根据跳转表的信息,在模式串和主串不匹配时,可以直接跳过一些字符,继续比较下一个字符。
KMP算法的时间复杂度为O(m+n),其中m和n分别为主串和模式串的长度。
KMP算法在主串长度较大时,相对于BF算法有较高的效率。
它的空间复杂度为O(k),其中k为模式串的长度,用于存储跳转表。
BM算法(Boyer-Moore Algorithm)是一种更为高效的字符串匹配算法。
BM算法的核心思想是尽可能地跳过更多的字符,而不是每次只移动一个字符。
BM算法借助两个启发式规则(坏字符规则和好后缀规则)来确定移动的步长。
坏字符规则根据字符在模式串中的位置,找到离坏字符最近的下标位置,从而确定移动的步长;好后缀规则根据已经匹配的后缀子串,找到离该子串最近的下标位置,从而确定移动的步长。
BM算法的时间复杂度为O(m+n),其中m和n分别为主串和模式串的长度。
BM算法在处理文本串相对固定的情况下有较高的效率,但是在模式串较短,主串较长的情况下,BM算法并不一定比KMP算法更高效。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
模式匹配的KMP算法详解
模式匹配的KMP算法详解
这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法。
大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白。
注意到这是一个改进的算法,所以有必要把原来的模式匹配算法拿出来,其实理解的关键就在这里,一般的匹配算法:
int Index(String S,String T,int pos)//参考《数据结构》中的程序
{
i=pos;j=1;//这里的串的第1个元素下标是1
while(i<=S.Length && j<=T.Length)
{
if(S[i]==T[j]){++i;++j;}
else{i=i-j+2;j=1;}//**************(1)
}
if(j>T.Length) return i-T.Length;//匹配成功
else return 0;
}
匹配的过程非常清晰,关键是当‘失配’的时候程序是如何处理的?回溯,没错,注意到(1)句,为什么要回溯,看下面的例子:
S:aaaaabababcaaa T:ababc
aaaaabababcaaa
ababc.(.表示前一个已经失配)
回溯的结果就是
aaaaabababcaaa
a.(babc)
如果不回溯就是
aaaaabababcaaa
aba.bc
这样就漏了一个可能匹配成功的情况
aaaaabababcaaa
ababc
为什么会发生这样的情况?这是由T串本身的性质决定的,是因为T串本身有前后'部分匹配'的性质。
如果T为abcdef这样的,大没有回溯的必要。
改进的地方也就是这里,我们从T串本身出发,事先就找准了T自身前后部分匹配的位置,那就可以改进算法。
如果不用回溯,那T串下一个位置从哪里开始呢?
还是上面那个例子,T为ababc,如果c失配,那就可以往前移到aba最后一个a的位置,像这样:
...ababd...
ababc
->ababc
这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。
这个当T[j]失配后,j应该往前跳的值就是j的next值,它是由T串本身固有决定的,与S串无关。
《数据结构》上给了next值的定义:
0 如果j=1
next[j]={Max{k|1<k<j且'p1...pk-1'='pj-k+1...pj-1'
1 其它情况
我当初看到这个头就晕了,其实它就是描述的我前面表述的情况,关于next[1] =0是规定的,这样规定可以使程序简单一些,如果非要定为其它的值只要不和后面的值冲突也是可以的;而那个Max是什么意思,举个例子:
T:aaab
...aaaab...
aaab
->aaab
->aaab
->aaab
像这样的T,前面自身部分匹配的部分不止两个,那应该往前跳到第几个呢?最近的一个,也就是说尽可能的向右滑移最短的长度。
OK,了解到这里,就看清了KMP的大部分内容,然后关键的问题是如何求nex t值?先不管它,先看如何用它来进行匹配操作,也就是说先假设已经有了ne xt值。
将最前面的程序改写成:
int Index_KMP(String S,String T,int pos)
{
i=pos;j=1;//这里的串的第1个元素下标是1
while(i<=S.Length && j<=T.Length)
{
if(j==0 || S[i]==T[j]){++i;++j;} //注意到这里的j==0,和++j的作用就知道为什么规定next[1]=0的好处了
else j=next[j];//i不变(不回溯),j跳动
}
if(j>T.Length) return i-T.Length;//匹配成功
else return 0;
}
OK,是不是非常简单?还有更简单的,求next值,这也是整个算法成功的关键,从next值的定义来求太恐怖了,怎么求?前面说过了,next值表达的就是T 串的自身部分匹配的性质,那么,我只要将T串和T串自身来一次匹配就可以求出来了,这里的匹配过程不是从头一个一个匹配,而是从T[1]和T[2]开始匹配,给出算法如下:
void get_next(String T,int &next[])
{
i=1;j=0;next[1]=0;
while(i<=T.Length)
{
if(j==0 || T[i]==T[j]){++i;++j; next[i]=j;/**********(2)*/}
else j=next[j];
}
}
看这个函数是不是非常像KMP匹配的函数,没错,它就是这么干的!注意到(2)语句逻辑覆盖的时候是T[i]==T[j]以及i前面的、j前面的都匹配的情况下,于是先自增,然后记下来next[i]=j,这样每当i有自增就会求得一个next[i],而j一定会小于等于i,于是对于已经求出来的next,可以继续求后面的nex t,而next[1]=0是已知,所以整个就这样递推的求出来了,方法非常巧妙。
这样的改进已经是很不错了,但算法还可以改进,注意到下面的匹配情况:
...aaac...
aaaa.
T串中的'a'和S串中的'c'失配,而'a'的next值指的还是'a',那同样的比较还是会失配,而这样的比较是多余的,如果我事先知道,当T[i]==T[j],那n ext[i]就设为next[j],在求next值的时候就已经比较了,这样就可以去掉这样的多余的比较。
于是稍加改进得到:
void get_nextval(String T,int &next[])
{
i=1;j=0;next[1]=0;
while(i<=T.Length)
{
if(j==0 || T[i]==T[j])
{ ++i;++j;
if(T[i]!=T[j]) next[i]=j;
else next[i]=next[j];//消去多余的可能的比较,next再向前跳
}
else j=next[j];
}
}
匹配算法不变。
到此就完全弄清楚了,以前老觉得KMP算法好神秘,真不是人想出来的,其实不然,它只不过是对原有的算法进行了改进。
可见基础的经典的东西还是很重要,你有本事‘废’了经典,就创造了进步。