数据结构kmp算法
㈠ 数据结构-串的模式匹配
串的模式匹配就是子串定位操作。给定两明亏个串s="s0 s1 ... s(n-1)"和t="t0 t1 ... t(m-1)"(其中n和m分别是串s和t的长度),在主串s中寻找子串t的过程称为模式匹配,t称为模式。如果在s中找到等于t的子串,则称匹配成功,返回t在s中的首次出现的下标位置;否则匹配失败,返回-1。
本文介绍三个串模式匹配算法,分别是简单回溯算法(Brute-Force,BF算法)、KMP算法、KMP算法的改进。
从主串s的第0个字符开始,与模式串t的第0个字符开始逐字符比较,不相同时回溯到模式串t的第0个和主串s的第1个字符,重新开始比较。以此类推,直到t的所有字符完成匹配,则匹配成功,否则匹配失败。
BF算法速度慢的原因是存在大量不必要的回溯,即在某一趟与t的匹配过程失败后,需要返回s串开始字符的下一字符重新开始比较,这对于某些模式串t来说是不必要的。例如,若s=12123123132,t=12313,在t与12 12312 3132中加粗子序列进行比较时,在 2 处发生失配,BF算法接下来将t与121 23123 132、1212 31231 32、12123 12313 2比较。由于t中的231、312与其开始的123并不相同,显然t与121 23123 132、1212 31231 32的比较是不必要的。
KMP算法就是利用模式串中与模式串开头部分子串的重复性来减少重复回溯,实现新一轮比较的直接跳转。 具体来说,KMP算法利用一个数组记录模式串中每一个字符前面有几个字符与模式串从头重复,在与s串比较失配时,直接跳转到重复子串的下一个字符继续比较,而不用跳转至模式串t的第0个字符。
算法步骤: ①计算跳转数组next。②利用KMP算法进行模式匹配。
next数组通过递推计算,即如果当前字符 t[j] 的前一个字符 t[j-1] 与其 next[j-1] 指向的字符 t[next[j-1]] 相同,意味着 t[j] 前的 next[j-1]+1 个字符与从 t[0] 到 t[next[j-1]] 的子串相同,因此 next[j]=next[j-1]+1 ;如果不相同,则递推至 t[next[j-1]] 的next值指向的字符,与 t[j-1] 比较,直到确认 t[j] 前与 t 串从头重复的数羡字符数,或者无重复字符标记为薯槐拍 0 。
注意此处的函数返回参数类型为int*,用于 返回一位数组 ,且返回的这个一位数组必须在函数中用static定义。
KMP算法进行模式匹配时,只需在回溯时将 j 指针赋值为 next[j] 。需要注意的是,若 next[j] 为 -1 ,则意味着 t[j] 前面没有与 t 从头重复的字符,且 t[j] 与 s[i] 失配,则 i 和 j 均加 1 。
考虑更特殊的模式串,还能进一步减少不必要的回溯次数。例如,s=111211112,t=11112,按照上述next的计算方式,next={-1,0,1,2,3}。当 i=3, j=3 时失配,此时 s[i]=2, t[j]=1 ,由于 next[j]=2 ,于是 j 跳转为 2 ,t=11 1 12与s=111 2 11112比较。由于 t[next[j]]=t[j] 也为 1 ,必然与 s[i]=2 不相同,显然这次回溯也不必要。
总结来说, 当失配的字符与待跳转的字符相同时,跳转一步并无意义,可再跳一步 ,即将当前字符置为跳转后字符的next值。
㈡ KMP是什么意思
一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)三人设计的线性时间字符串匹配算法。这个算法不用计算变迁函数δ,匹配时间为Θ(n),只用到辅助函数π[1,m],它是在Θ(m)时间内,根据模式预先计算出来的。数组π使得我们可以按需要,“现场”有效的计算(在平摊意义上来说)变迁函数δ。粗略地说,对任意状态q=0,1,…,m和任意字符a∈Σ,π[q]的值包含了与a无关但在计算δ(q,a)时需要的信息。由于数组π只有m个元素,而δ有Θ(m∣Σ∣)个值,所以通过预先计算π而不是δ,使得时间减少了一个Σ因子。
㈢ 数据结构与算法——字符串匹配问题(KMP算法)
KMP算法也是比较着名的模式匹配算法。是由 D.E.Knuth,J.H.Morrs 和 VR.Pratt 发表的一个模式匹配算法。可以大大避免重复遍历的情况。
如果使用暴风算法的话,前面五个字母完全相等,直到第六个字母 "f" 和 "x" 不相等。如下图:
T = “abcdex”
j 123456
模式串 abcdex
next[j] 011111
T = "abcabx"
j 123456
模式串T abcabx
next[j] 011123
T = "ababaaaba"
j———————123456789
模式串T——— ababaaaba
next[j]————011234223
T = "aaaaaaaab"
j———————123456789
模式串T——— aaaaaaaab
next[j]————012345678
next数组其实就是求解字符串要回溯的位置
假设,主串S= “abcababca”;模式串T=“abcdex”,由以上分析得出next数组为011111,next数组意味着当主串与模式串不匹配时,都需要从第一个的位置重新比较。
KMP算法也是有缺陷的,比如主串S=“aaaabcde”,模式串T= “aaaaax”。next的数组就是012345;
当开始匹配时,当i= 5,j = 5时,我们发现字符"b"与字符“a”不相等,如上图,j = next[5] = 4;
由于T串的第二、三、四、五位置的字符都与首位“a”相等,那么可以用首位next[1]的值去取代与它相等的后续字符的next[j],那么next数组为{0,0,0,0,0,5};
在求解nextVal数组的5种情况
㈣ KMP是什么意思
kmp算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是根据给定的模式串W1,m,定义一个next函数。next函数包含了模式串本身局部匹配的信息。
完全掌握KMP算法思想
学过数据结构的人,都对KMP算法印象颇深。
㈤ 算法-KMP
大一下参加学校ACM预备队集训的时候首次接触KMP算法,当时看了很多介绍文章,仍然不是很理解其实质,只是简单地套模板AC题目,待大二数据结构与算法课堂上再听老师介绍一次,才恍然大悟其实KMP也就是那么回事嘛。但当初为啥看那么多文章都没弄明白呢?正巧最近和朋友聊天时他告诉我他对KMP不是很理解,于是打算自己写一篇文章,巩固自己对KMP的认识,也希望能够帮助更多朋友理解KMP。
在开始之前,需要知晓的概念:
前缀:以原串串头为自身串头的子串,如 的前缀有:
后缀:以原串串尾为自身串尾的子串,如 的后缀有:
注意:字符串前后缀都不包括该串本身
给你一个文本串T(Text String)
再给你一个模式串P(Pattern String)
问该模式串是否在文本串中,怎么找?
一开始只好分别从文本串与模式串的串头开始逐字母比较
二者相同,再比较T串与P串的下一位
如此反复
如果一直这么顺利,两串对应位置的字符总相同,待P串中最后一个字符也匹配完毕,说明该模式串在文本串中存在,耶( •̀ ω •́ )y超开心,查找结束。但,大多数匹配过程不会如此顺利,在该例中,当匹配进行至
很明显,失配了。现在怎么办?按朴素思想,将P串相对T串整体右移一位,重新开始匹配,即
但这种算法效率无疑是十分低下的。设T串长度N,P串长度M,则朴素算法时间复杂度为O(MN)
已知的重要信息并没有被使用——已匹配的字符串前缀
在上例中,当P串最后一个字符匹配失败时,其已有包含七个字符的 前缀子串S 匹配成功
完全可以利用前缀子串S做点什么。观察到在S串
中,有相同前后缀,即下图蓝色部分
而S串各字符又与T串中对应字符相同,即有
当失配发生后,直接将P串右移四位使S串蓝色后缀部分对齐T串中蓝色前缀部分
从图中红框部分继续尝试匹配,发现再次失配。这次,已匹配成功的前缀串S为
而在该串中没有相同的前后缀,只能将P串串头移至失配处进行比较
再次失配。此时前缀串S为空串,只好如朴素算法般将P串整体右移一位,重新开始比较
匹配成功。于是又按照之前的步骤往下匹配,直至再次失配或匹配成功
后续步骤同上,不再赘述
上述示例已展现,KMP算法的精髓在于对已匹配成功的前缀串S的利用
在朴素算法中,匹配失败了,T串待匹配字符会回溯
T串原本已匹配至T[7] = 'X',但是因为失配,需回溯到T[1] = 'b'重新开始匹配
而在KMP算法中,若P[M]与T[K]匹配失败,K不会回溯。既然匹配过程是从T[0]开始逐渐向右进行的,至T[K]失配发生时,T[0]至T[K-1]早已匹配过,何必再回溯过去重复匹配呢?于是乎,就如问题引入部分展示般
每当失配发生,我们总是去关注P串中已匹配成功的前缀串S
因为该前缀串是匹配成功的,说明在T串中必定存在与该前缀串相同的子串,记为S'
若S串中存在相同前后缀
则S'串必然也存在此相同前后缀
所以只需将P串右移四位,使得S串的该相同前缀对齐S'串的该相同后缀
再尝试比较T[7]与P[3]
至于T[7]与P[3]是否能够匹配另说(当然,本例中一看就知道没匹配上),但通过对前缀串S的利用,成功省去了P串右移一位、两位和三位后的无效匹配
继续深入思考,给定一个具体的P串,其第N位的前缀串S内容是固定的,则S是否存在相同前后缀、相同前后缀的长度与内容也是确定的。换言之,对于一个具体的P串,当其与给定T串匹配至P[N]失配,P串应右移几位再次与T串进行匹配也是确定的。我们完全可以使用一个数组记录当P[N]失配后,应当使用N之前的哪一位再来与T串进行匹配,以此提高匹配效率,记该数组为Next数组
定义Next[i] = j表示当P串中第i位失配后,跳转至P串第j位再次尝试匹配
还是以之前的P串为例,它的Next数组求出来应为
取下标5为例,其前缀串为
最长相同前后缀为
若P[5]失配,应跳转至P[1]再次尝试匹配(最长相同前缀对应P[0],则取其后一位P[1],若存在多位,则取最后一位的下一位),P[5]的前一个字符P[4]对应字符'a',而P[1]前一个字符P[0]同对应字符'a',保证了P[1]之前字符与T串中对应字符保持匹配。所以Next[5] = 1,其余下标对应Next数组值同如此求。
特别地,规定Next[0] = -1。而对于除下标0外的任意下标N,Next[N]的含义是 前N-1个已匹配成功的字符构成的前缀串S中,最长相同前后缀长度。 所以若在下标为N处匹配失败了,则应前往Next[N]所对应的下标处匹配。
具体地,以下图所示为例,P[6]与T[6]失配
而Next[6] = 2,所以使用P[2]再次尝试与T[6]进行匹配
当求出P串Next数组后,便可快速进行与T串的匹配
现在问题只剩下如何求Next数组,注意到Next数组既然只与P串本身相关,与文本串T无关,故令P串与自身匹配即可求得
考虑字符串
其Next数组应为
令其与给定文本串相匹配
当匹配进行至
失配,于是跳转至P[Next[3]] = P[1]处再次尝试匹配
再度失配,也必然失配
问题在于不该出现P[N] =P[Next[N]]
若P[N] =P[Next[N]],则P[N]失配后使用P[Next[N]]再次尝试匹配,由于P[N] =P[Next[N]],P[N]匹配失败,P[Next[N]]必然也失败
因此,若出现P[N] =P[Next[N]]情况,则令Next[N]=Next[Next[N]]
本例中该字符串新Next数组为
当匹配进行至
失配,于是跳转至P[Next[3]] = P[0]处再次尝试匹配
省去了之前跳转至P[1]处的无效匹配
设T串长度M,P串长度N,由于KMP算法不会回溯,分析易知时间复杂度为O(m+n)
对于P[N],若其前缀串S含相同前后缀F,且F长度为n(n>1),Next[N]可以取1至n中任意值,为最大化匹配效率考虑,总是取最大相同前后缀以提高效率,节省时间
㈥ kmp算法难吗是什么级别
KMP算法是我们数据结构串中最难也是最重要的算法。难是因为KMP算法的代码很优美简洁干练,但里面包含着非常深的思维。真正理解代码的人可以说对KMP算法的了解已经相当深入了。而且这个算法的不少东西的确不容易讲懂,很多正规的书本把概念一摆出直接劝退无数人。这篇文章将尽量以最详细的方式配图介绍KMP算法及其改进。文章的开始我先对KMP算法的三位创始人Knuth,Morris,Pratt致敬,懂得这个算法的流程后你真的不得不佩服他们的聪明才智。