KMP算法和手算next数组就不多说了,主要是如何代码实现求next数组~
next数组
next[j]: j指向模式串的第j个位置(从1开始),next[j]表示模式串第j个字符与主串不匹配时, j要回溯到第几个位置 next[0]弃用
next[j]的值:第j个字符前面的j-1个字符组成的子串 的最大公共前后缀的长度+1(很重要!!)
next[1] = 0; next[2] = 1;
模式串第j个字符不匹配时,指向主串的i位置不变,向后移动模式串,将最大公共前缀对齐最大公共后缀,j就指向后缀的下一个字符
↓
a b a b a c ......
a b a b a a "ababa"最大公共前后缀为"aba"
a b a b a a(移动后)
如何求next数组呢?
比如求next[16]: 已知next[15](比如next[15]=7),即前14个字符组成的子串的最大公共前后缀长度为6, 比较第7(next[15])个字符和第15个字符
若相等,则前15个字符的最大公共前后缀为7, 即next[16] = next[15] + 1 = 8;
若不相等,比如next[7]=3, 即前6个字符组成的子串的最大公共前后缀长度为2, 比较第3(next[7])个字符和第15个字符
若相等,则前15个字符的最大公共前后缀为3, 即next[16] = next[7] + 1 = 4;
若不相等,比如next[3]=1, 即前2个字符组成的子串无公共前后缀, 也就是前15个字符没有公共前后缀, 即next[16] = 1;
↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
a b c c a b * * a b c c a b * *
↑ ↑
next[15]=7
KMP,"看门牌",j的"门牌"就是next[j], 求next[k+1],看第k个字符和它的"门牌号码"(第next[k]个字符)是否相等
画图就很好理解了:这个up主讲得挺好
next数组进一步优化
当第 j 个字符与第 k = next [ j ] 个字符相等时,next [ j ] 可直接等于next [ k ]
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define MAX 255
//串
typedef struct {
char ch[MAX];
int length;
}SString;
void GetNext(SString t, int next[]) {
int i = 1; //i=1指向模式串t的第一个字符位置,也是next数组的索引
int j = 0;
next[1] = 0;
while (i < t.length) {
if (j == 0 || t.ch[i] == t.ch[j]) {
next[++i] = ++j;
if (t.ch[i] == t.ch[j]) //进一步优化
next[i] = next[j];
}
else
j = next[j]; //若一直不相等,最终j = next[1] = 0
}
}
//若主串中存在与模式串完全相同的子串,返回第一次出现的位置,不存在则返回0
int Index_KMP(SString s, SString t, int next[]) {
int i = 1, j = 1; //i = 1指向s.ch[0], j = 1指向t.ch[0]
while (i <= s.length && j <= t.length) {
//当第一个元素匹配失败时,匹配下一个字符 令j = 0后i++,j++
if (j == 0 || s.ch[i - 1] == t.ch[j - 1]) {
i++;
j++;
}
else {
j = next[j];
}
}
if (j > t.length)
return i - t.length;
else
return 0;
}
int main()
{
SString s = { "fjauirnvoagnirhnabaabchjk",25 };
SString t = { "abaabc",6 }; //17
int next[10] = { 0 };
GetNext(t, next);
printf("%d", Index_KMP(s, t, next));
return 0;
}