https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
3.3.1 寻找最长前缀后缀
如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:
也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为(下简称《最大长度表》):
3.3.2 基于《最大长度表》匹配
因为模式串中首尾可能会有重复的字符,故可得出下述结论:
失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值
下面,咱们就结合之前的《最大长度表》和上述结论,进行字符串的匹配。如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:
- 1. 因为模式串中的字符A跟文本串中的字符B、B、C、空格一开始就不匹配,所以不必考虑结论,直接将模式串不断的右移一位即可,直到模式串中的字符A跟文本串的第5个字符A匹配成功:
-
2. 继续往后匹配,当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据《最大长度表》可得失配字符D的上一位字符B对应的长度值为2,所以根据之前的结论,可知需要向右移动6 - 2 = 4 位。
-
3. 模式串向右移动4位后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一位字符B对应的最大长度值为0,所以向右移动:2 - 0 =2 位。
- 4. A与空格失配,向右移动1 位。
-
5. 继续比较,发现D与C 失配,故向右移动的位数为:已匹配的字符数6减去上一位字符B对应的最大长度2,即向右移动6 - 2 = 4 位。
- 6. 经历第5步后,发现匹配成功,过程结束。
通过上述匹配过程可以看出,问题的关键就是寻找模式串中最大长度的相同前缀和后缀,找到了模式串中每个字符之前的前缀和后缀公共部分的最大长度后,便可基于此匹配。而这个最大长度便正是next 数组要表达的含义。
3.3.3 根据《最大长度表》求next 数组
由上文,我们已经知道,字符串“ABCDABD”各个前缀后缀的最大公共元素长度分别为:
而且,根据这个表可以得出下述结论
-
失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值
上文利用这个表和结论进行匹配时,我们发现,当匹配到一个字符失配时,其实没必要考虑当前失配的字符,更何况我们每次失配时,都是看的失配字符的上一位字符对应的最大长度值。如此,便引出了next 数组。
给定字符串“ABCDABD”,可求得它的next 数组如下:
把next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。意识到了这一点,你会惊呼原来next 数组的求解竟然如此简单:就是找最大对称长度的前缀后缀,然后整体右移一位,初值赋为-1(当然,你也可以直接计算某个字符对应的next值,就是看这个字符之前的字符串中有多大长度的相同前缀后缀)。
换言之,对于给定的模式串:ABCDABD,它的最大长度表及next 数组分别如下:
根据最大长度表求出了next 数组后,从而有
失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值
而后,你会发现,无论是基于《最大长度表》的匹配,还是基于next 数组的匹配,两者得出来的向右移动的位数是一样的。为什么呢?因为:
-
根据《最大长度表》,失配时,模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大长度值
-
而根据《next 数组》,失配时,模式串向右移动的位数 = 失配字符的位置 - 失配字符对应的next 值
-
其中,从0开始计数时,失配字符的位置 = 已经匹配的字符数(失配字符不计数),而失配字符对应的next 值 = 失配字符的上一位字符的最大长度值,两相比较,结果必然完全一致。
-
所以,你可以把《最大长度表》看做是next 数组的雏形,甚至就把它当做next 数组也是可以的,区别不过是怎么用的问题。
void getnext()
{
int j=-1;
next[0]=-1;
for(int i=1;i<lena;i++)//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
{
while(j != -1 && s[i]!=s[j+1] )
{
j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
}
if(s[i] == s[j+1])
{
j++;
}
next[i]=j;
}
}
int kmp(char *text, char *pattern)
{
int ans=0;
int n=strlen(text),m=strlen(pattern);
getnext(pattern);
int j=-1;
for(int i=0; i<n; i++)
{
while( j != -1 && text[i]!=pattern[j+1] )
{
j=next[j];
}
if(text[i] == pattern[j+1])
j++;
if(j==m-1)
{
//cout<<true<<endl;
ans++;
j=next[j];//后退一步,i++,接着匹配,当做为最后一个匹配失败,继续向后匹配
}
}
return ans;
}
next[i]是子串s[0......i]的最长相等前后缀的前缀的最后一位的下标
//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作,当 j + 1 失配后,j应该回退到的位置!
例如:
i 0 1 2 3
a b a b
a b a b
j -1 0 1 2 3 我们得知,前后ab相等,当匹配到 i=4 时,出现不匹配时,i=next[i]也就是说返回到s[1]这个位置来继续匹配算法
#include <stdio.h>
#include <string.h>
#include<iostream>
#include <algorithm>
using std::min;
using namespace std;
//int lena, lenb;
char *a, *b;
int next[1010];
void read()
{
//scanf("%s", a );
a="ababaab";
b="aaaaaababaab";
//lena = strlen(a);
//lenb = strlen(b);
}
void getnext(char *s)
{
int lena = strlen(s);
int j=-1;
next[0]=-1;
for(int i=1;i<lena;i++)//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
{
while(j != -1 && s[i]!=s[j+1] )
{
j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
}
if(s[i] == s[j+1])
{
j++;
}
next[i]=j;
}
}
bool kmp(char *text, char *pattern)
{
int n=strlen(text),m=strlen(pattern);
getnext(pattern);
int j=-1;
for(int i=0;i<n;i++)
{
while( j != -1 && text[i]!=pattern[j+1] )
{
j=next[j];
}
if(text[i] == pattern[j+1])
j++;
if(j==m-1)
{
cout<<true<<endl;
return true;
}
}
return false;
}
int main()
{
read();
//getnext(a);
kmp(b,a);
return 0;
}
next的再优化!!!1
void getnext(char *s)
{
int lena = strlen(s);
int j=-1;
next[0]=-1;
for(int i=1; i<lena; i++) //next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
{
while(j != -1 && s[i]!=s[j+1] )
{
j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
}
if(s[i] == s[j+1])
{
j++;
}
if( j==-1 || s[i+1] != s[j+1] )//j==-1不能再回退
next[i]=j;
else
next[i]=next[j];//再回退
}
}
此时nextval[i]含义为当i+1匹配失位后,i应当回退到的最佳位置
i 0 1 2 3 4
a b a b a b
a b a b a b
j -1 0 1 2 3 4
if( j==-1 || s[i+1] != s[j+1] ) //j==-1不能再回退
next[i]=j;
else
next[i]=next[j];//再回退
假设abab与母串已经匹配失败,到了b时不成功了!
通常回退到i=0,值为a处,但是结果也是GG!
因为结果与b匹配失败也会与后面拉上来的b匹配失败!只有不相同时回退才有效!
ababab这里我们看见next[-1,-1,-1,-1,-1,3]