kmp
kmp算法就是为了避免字符串匹配中出现重复遍历的情况
kmp匹配算法的原理
S串:ababaaaba
T串:ababaac
如果采用bf(暴力匹配)的话,第一次已经匹配到c才发现匹配不上,然后就会重新将T串的首字母a与S串的第二个字母b进行匹配,这样就会增加很多无用的匹配,时间复杂度也大。如图
但是如果采用kmp算法,就不需要一个一个比较。kmp的核心就在于最长公共前后缀,但是为什么要用最长公共前后缀?最长公共前后缀是字符串移位的关键,因为我们要把前缀的位置直接移到后缀的位置,来举个例子
现在比较到母串比较到B,子串比较到A,发现不一样,又发现,字串中有公共前后缀AB。
所以直接就把前面的AB移动到后面的AB当中。
这样可以保证当前指针的前面上下是匹配的
这时候就有人有疑问了,万一中间的也有匹配上的呢,但事实是不会有的。可以走一下看看,现在子串往前走一步
发现匹配不上,就再走一步
还是不匹配,再走一步
发现只有公共前缀移动到公共后缀时才匹配上了
kmp算法利用了子串本身的特征,当子串的第n位匹配不上时,接下来就要寻找能与n位前面的i个(i表示子串(n-1)对应的最长公共前后缀的长度)字符匹配上的,也就是找子串的前i位,子串的前i位与母串n位置的前i位对其一定是匹配的,所以只需要从第1+1的位置继续匹配。所以next数组就是最长公共前后缀加1.
next数组的求法
我们能确定next的数组的第0位是-1,后面求解每一位的next的值时
根据前一位进行比较,从第一位开始
将其前一位的字符与其前一位的next值所对应的字符相比较,如果相等
则该位的next的值就是前一位的next的值加1,如果不等,
继续寻找next值对应的内容来与前一位进行比较,
直到找到某个位上内容的next的值对应的内容与前一位相等
则这个位对应的值加上1即为需求的next值
如果找到第一位都没有找到与前一位相等的内容,那么求解位上的next值为next[0]加1
void getnext(char* t, int* next) {
int l = strlen(t);
next[0] = -1;
int i = 1;
int k = 0;
while (i <= l) {
if (k == 0||t[next[k]] == t[i - 1] ) {
next[i] = next[k] + 1;
i++;
k = i - 1;
}
else k = next[k];
}
}
next_value的求法
nextval数组是在next数组的基础上求得的,如果i所指向的字符,与next[i]所指向的字符相等,就可以直接把nextval[next[i]]的值赋给nextval[i],如果不相等,nextval[i]就是next[i]
void getnext_val(char* t, int* next, int* nextval) {
int i = 1;
nextval[0]=-1;
int l = strlen(t);
while (i <= l) {
if (t[next[i]] == t[i]) nextval[i] = nextval[next[i]];
else nextval[i] = next[i];
i++;
}
}
kmp的主要算法
如果两个指针指向的字符串相等,则两指针加加,否则,j回退到nextval[j]的位置
int kmp(char* s, char* t,int* nextval) {
int i = 0;
int j = 0;
int l1 = strlen(s);
int l2 = strlen(t);
while (i < l1 && j < l2) {
if (j == -1||s[i] == t[j] ) {
i++;
j++;
}
else j = nextval[j];
}
if (j == l2) return i - l2 ;
else return -1;
}
接下来是完整的kmp算法的代码
#include<stdio.h>
#include<string.h>
//当字符串索引是从0开始的,next和nextval数组下标都是从0开始的
//且索引为1的时候两个数组的值都是-1,因为next数组第一个元素是-1
//所以要遍历整个字符串,所以是<=,nextval数组也是一样,当到kmp时,就不用=了
void getnext(char* t, int* next) {
int l = strlen(t);
next[0] = -1;
int i = 1;
int k = 0;
while (i <= l) {
if (t[next[k]] == t[i - 1] || k == 0) {
next[i] = next[k] + 1;
i++;
k = i - 1;
}
else k = next[k];
}
}
void getnext_val(char* t, int* next, int* nextval) {
int i = 1;
nextval[0]=-1;
int l = strlen(t);
while (i <= l) {
if (t[next[i]] == t[i]) nextval[i] = nextval[next[i]];
else nextval[i] = next[i];
i++;
}
}
int kmp(char* s, char* t,int* nextval) {
int i = 0;
int j = 0;
int l1 = strlen(s);
int l2 = strlen(t);
while (i < l1 && j < l2) {
if (s[i] == t[j] || j == -1) {
i++;
j++;
}
else j = nextval[j];
}
if (j == l2) return i - l2 ;
else return -1;
}
int main(void) {
char s[100],t[100];
scanf("%s", s);
scanf("%s", t);
int next[100] = { 0 };
getnext(t, next);
int nextval[100];
getnext_val(t, next, nextval);
int m = kmp(s, t,nextval);
printf("%d", m);
return 0;
}