kmp详细讲解
字符串匹配算法有BF、RK、KMP、BM、Sunday!
BF大家都清楚,算法比较暴力,首先将原字符串和子串左端对齐,逐一比较;如果第一个字符不能匹配,则子串向后移动一位继续比较;如果第一个字符匹配,则继续比较后续字符,直至全部匹配。BF算法虽说解决了问题,但用时比较长。So咱们讲讲KMP。
KMP对萌新来说有点难度,不过慢慢来总会理解的。
下面我讲一下KMP匹配步骤:
很明显第一趟之后可以直接跳到第三趟,那该怎么实现呢?
咱们就引进一个next数组,它用于字串跳匹配的下标。比如上图我可以计算出主串的next数组是:-1,0,0,1,2。
大家就疑惑next数组怎么求,该怎么用,为什么可以这么用。
咱们先来讲讲求法吧:
这里咱们引入前缀与后缀的概念:
前缀是指除最后一个字符外,一个字符串全部头部组合;后缀是指除第一个字符外,一个字符串全部尾部组合。以”abcda”为例:
“ab”的前缀为[a],后缀为[b],共有元素的长度为0;
“abc”的前缀为[a, ab],后缀为[bc, c],共有元素的长度0;
…
“abcda”的前缀为[a, ab, abc, abcd],后缀为[bcda, cda, da, a],共有元素为”a”,长度为1;
而这个长度就是咱们的next值,第一个字符默认为-1。
咱们接下来讲讲用法吧:
依次匹配时,当字符不匹配,咱们直接讲主串下一个字符和子串该不匹配字符的next值作为下标对应的子串中的字符接着往下匹配。就如上两幅图所示,当第一躺第三个字符不匹配时,子串第三个字符next值为0,咱们直接跳到子串第一个字符(下标为0)和主串下一个字符匹配,也就是第三趟。
为什么会这样匹配呢:
咱们想想啊,next是最大前缀和后缀相同值,而匹配时前面的匹配对了,后缀与前缀有相同的,自然直接跳过,或许解释的有些抽象,但希望大家好好想想就应该能想明白。
Next求法(咱也不多说,直接上代码):
void getnext(char *t,int *next)
{
int j=-1;
int i=0;
next[0]=-1;
while(i<strlen(t))
{
if(j==-1||t[i]==t[j])//相等即匹配,i和j都往后移
{
i++;
j++;
next[i]=j;
}
//这里其实也相当于字符匹配,匹配不成功,j跳到next[j]
else
{
j=next[j];
}
}
}
接下来是next使用,咱们就在主串中看是否有字串。
#include<iostream>
#include<string.h>
using namespace std;
void getnext(char *t,int *next)
{
int j=-1;
int i=0;
next[0]=-1;
while(i<strlen(t))
{
if(j==-1||t[i]==t[j])//相等即匹配,i和j都往后移
{
i++;
j++;
next[i]=j;
}
//这里其实也相当于字符匹配,匹配不成功,j跳到next[j]
else
{
j=next[j];
}
}
}
int main()
{
char *t1,*t2;
int *next;
t1=new char[10];
t2=new char[10];
next=new int[10];
cin>>t1>>t2;
getnext(t2,next);
int i=0,j=0;
int n1=strlen(t1),n2=strlen(t2);
while(i<n1&&j<n2)//循环条件别写strlen(t1),否则只会判断一次
{
if(t1[i]==t2[j]||j==-1)
{
if(j==(strlen(t2)-1))
{
cout<<"yes"<<endl;
return 0;
}
i++;
j++;
}
else
{
j=next[j];
}
}
cout<<"no"<<endl;
return 0;
}
效果如下: