今天复习数据结构,发现自己之前忽视了很久的一个算法,关于求串的匹配算法。这里有两种解决办法。
其一是常规解决思路对串进行挨个匹配,若以i指向主串,j指向匹配串,则在匹配过程中需要不停的回溯i指针,假设T={ababcabababab} S={ababa}
我们可以发现该算法在匹配时,一旦遇见不匹配的情况指针i就会回溯,同时产生了大量不需要的匹配过程。尤其是当遇见如T={AAAAAAAAAAAAAAAAAAAAAAB} S={AAAB}这类情况时时间复杂度为O(n*m)
代码实现
方法二KMP
这种改进算法是由D.E.Knuth与VR.Pratt和J.H.Morris同时发现的,因此人人们称它为克努特-莫里斯-普拉特操作(简称KMP算法)。此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作。其改进在于:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。这是书上的解释、、、谁tm能看懂
这里的尽可能远的意思时,我们拿模式串和主串进行比对时对于i指针,即指向主串的指针不会产生回溯,通过移动模式来匹配下面举例说明
在第一趟匹配的过程中我们发现当i = 2时匹配失败,此时j = 2;按照我们刚才说的对模式进行移动,至于移动的多少可以看这里模式对应的next表。这里先给出next表,接下来会解释next的来历现在只要知道怎么用就好了。next = {0,0,1,1,2,0,1,0}当j = 2时匹配失败我们需要找到模式的偏移量,对应着表中的第j-1个元素我们发现其值为零那么就将模式向左移一位,即j = 1;
在第二趟匹配中,当i = 2,j = 1时匹配失败,模式左移一位;
第三趟匹配中,i = 8, j = 6匹配失败,对照着表第j-1个元素值为2将模式左移3位,即j = 3;
第四趟匹配全部完成。
那么为什么要这么移动?next的值究竟代表着什么?
先给大家介绍前缀码和后缀码拿模式来讲 S={abaabca}
子串
a 没有前缀码和后缀码
ab 前缀码a,后缀码b
aba 前缀码{a,ab}后缀码{ba,b}
abaa 前缀码{a,ab,aba}后缀码{baa,aa,a}
abaab 前缀码{a,ab,aba,abaa} 后缀码{b,ab,aab,baab}
abaabca 前缀码{a,ab,aba.abaa,abaab,abaac} 后缀码{a,ca,bca,abca,aabca,baabca}
next表中存储的值即为当前子串中公共前后缀码的长度,比如aba 前后缀码没有相同的所以next[2]=0(ps:这里是从零开始的数组) abaa 前后缀码有一个相同a 所以next[3] = 1;以此类推得到next表;
那么我们为什么求得next表?
next的表存储的是当前子串的公共前后缀码个数(k),意味着next[i]=k意味着S[0...k-1] = S[i-k+1] {abaab里面 前面的ab与后面的ab}这里很重要!!!!!!这里意味着如果我们当前已经匹配完成的字符里已经包含到了abaab,这里假设pos指向主串,j指向模式; 那么就有T[pos-j......pos-1]==S[0....j-1],并且T[pos-2,pos-1] == a,b S[0,1]==a,b;这两个值相同的!!!!!我们看一下是怎么来的由于T[pos-j...pos-1]==S[0...j-1] 得到①T[pos-j,pos-j+1] == S[0,1]==a,b; ②T[pos-2,pos-1] == S[j-2,j-1] ③S[0,1]==S[j-2,j-1];由①②③可以得到 T[pos-2,pos-1] == a,b S[0,1]==a,b,我们得到结论不需要再去关注S[0,1]只需要关注S[2]与T[pos]这样我们的的pos指针就不需要回溯。还记得我们的next表吗?abaab对应的next[4] = 2; 即告诉我们前两个字符不需要在进行匹配直接从第三个字符开始匹配。大家可以对应例子中的第三趟进行验证。
我们已经知道了next表的用法,他的存在能帮我们有效的减少不必要的校对,那么剩下的问题是我们怎么求得next表呢?next表的求取方法是整个KMP方法的难点。
我们知道next表中存储的是当前字符串的最长公共前后缀码的个数。有这样的关系 S[i+1]==S[j+1] next[j+1] = next[j]+1; 但是遇见 S[i+1]!=S[j+1]时 next[j+1]的值我们怎么求呢?
求next的重点就在这里 与前面利用next[]进行匹配的思路一样我们发现next[j]的值代表S[0..j]这个字符串中最大公共前后缀码,设next[j] = k;那么就有S[j-k+1....j]==S[0..k-1]。现在已经知道S[i+1] != S[j+1]了,那么我们下一步就是缩短S[j+1]前缀码的范围再尝试去匹配。这里就有k = next[k-1]!!!!!!!!重点就在这里由S[j-k+1....j]==S[0..k-1]得知S[k-1]==S[j];接下来就是对比S[j]与S[k]按照上面的顺序进行匹配;