注:模式串指我们要找是不是出现的串,主串指我们匹配的对象。
简介:
一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)三人设计的线性时间字符串匹配算法。
大致原理:
其实就是利用已经匹配过的字符,也就是在中间的每一个位置,就断开了。利用前面的部分去设置,下次寻找的点。
普通的就是这个失败,就返回刚开始的位置的下一个。继续寻找。不断的,不断的。。。
KMP的话,就是看你找过且对的上的部分的规律,通过这个规律,我们判断下一次找的点的位置,从模式串里面的那个位置开始。
比如下面的一个例子:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
0 | 0 | 1 |
当1不匹配的时候,我们是返回第二个位置接着寻找呢?(当然KMP也是这样的,但是我们指只要匹配不成功,就下面一个位置继续,重新开始的那种普通匹配。)
0 | 0 | 0 | 0 | 0 | 0 | 1 |
0 | 0 | 1 |
以上的寻找,怎么样改善呢?
KMP关键点:next数组
他通过判断制定一个next的规则,进行模式串下一次的位置判断点。就可以直接判断。从而实现O(n+m)的时间复杂度。
next的重点解析:
重点:每一个next的值,都是表示前看,后看的最长公共长度。 (以该点后看,和从开始往后看)
注:0特殊注意一下,1表示如果不相等,要返回的位置 = 1 -1=0,第一个0表示边界,后面的表示返回的位置(下标从1开始)
解释一下我的next的意思:
就几个例子,解释一下。好吧。
abcdefg这个例子:next为0 1 1 1 1 1 1;
aaaaa这个例子:next就是 0 1 2 3 4 了。
(最后一个a的next为4,表示如果不相等就要返回i = 3的位置区)
next的讲解:
我举一个例子:假设条件是 子串: 0001,主串:00000001.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
0 | 0 | 0 | 1 |
现在的情况是:匹配到了j = 4的时候,二者不相同。那么我们应该让j跳到哪里去呢?(普通寻找和KMP的最大区别点)
普通寻找:j = 0,(从头开始继续),KMP:观察结束的位置,观察已经匹配好了的串的规律。
由题目可以看出:000 已经匹配好了的。那么我是不是可以认为这样呢?
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
0 | 0 | 0 | 1 |
前面 vision[0~j-1]是已经匹配好了的,说明如果我从匹配好了的里面找出"折点",是不是就可以不用从新开始了呢?
折点:在以匹配的串中,找出循环点的位置,(从开始寻找和自己点往前寻找比如abaab,折点在第三个a上面)
这个较特殊:我们换一个 子串:abaabc 主串:abaabaabc这个来找这个折点
a | b | a | a | b | a | a | b | c |
a | b | a | a | b | c |
我们观看到i = j = 5的时候断了。这个时候,我们就要分析了折点在哪?
比如不存在折点问题:那么我想问你换一个位置重新匹配可以成功吗?答案显然是不存在的。
比如 abcdef,在f断了。那也表示着abcde匹配成功了,还表示着如果移一个位置重新开始,从b,c,d,e,f开始接着寻找可能找到吗?答案:不可能。
回到上面的问题:寻找折点:abaaab匹配成功了,折点在第一个b上面。
(前面后面看只有ab了。考虑第三个的话,前面为:aba,后面:aab,不相等。所以在b上面。)
那么我们下次寻找就不需要从新寻找,只需要从b的后面一个开始接着匹配就可以了。
时间复杂度与作用分析:
next差不多也就是讲完了。KMP的玄机也就是这样了。
当我们知道这些next,又有什么用呢?
当然有用了,结合next数组,在加上主串的寻找我们可以实现O(n+m)的时间。也就实现了时间复杂度的缩小的作用了。
还可以通过next反复在主串中寻找重复的子串,(记录位置,判断个数,最长前缀等等问题)
代码版署:
要想重复的搜索,有多少个。
只需要找到之后让k = 0.重新找。(if(k == len) k = 0,cnt++;)
我的代码是学校的oj,以0为开头的KMP算法,有可能看不懂,不过没关系,看得懂前面的就可以了。(next[i],表示的是0到i-1的位置的相同长度!0特殊注意一下,1表示如果不相等,要返回的位置)
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
void get_next(char *str,int len,int *next)
{
int k;
next[0] = 0;
k = 0;
for (int i=1; i < len; i++)
{
while(k > 0 && str[i] != str[k])
k = next[k-1];
if (str[i] == str[k])
k++;
next[i] = k;
}
}
void print_next(int *next,int len)
{
for(int i=0; i<len; i++)
printf("%d ",next[i]);
printf("\n");
}
int KMP(char *str,int slen,char *vision,int len,int *next)
{
int j = 0,k=0;
for(int i=0; i<slen; i++)
{
while(j>0 && str[i] != vision[j])
j = next[j -1];
if(str[i] == vision[j])
j++;
if(j==len)
{
k ++;
j = next[j-1];
}
}
return k;
}
int main()
{
char str[1000005],vision[10005];
int slen,len,next[10005],y;
scanf("%d",&y);
while(y--)
{
scanf("%s%s",&vision,&str);
len = strlen(vision);
slen = strlen(str);
get_next(vision,len,next);
printf("%d\n",KMP(str,slen,vision,len,next));
}
return 0;
}
代码版署,也就结束了。学校的测试过了。不过我希望博友们,可以留言,指正一下可能的错误点或者测试用例。
最后留言:
其实,我就是听我们老师讲了一节课,自己的总结。怕忘了。
最后,谢谢,各位大佬的观看!