文章目录
一起来学数据结构——KMP算法
首先我们先来看一下BF算法。
BF算法
char* my_strstr(const char* arr1, const char* arr2)
{
char* str1 = (char*)arr1;
char* str2 = (char*)arr2;
char* cp = str1;
while (*cp)
{
str1 = cp;
str2 = arr2;
while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2)
{
str1++;
str2++;
}
if (*str2 == '\0')//如果将目标元素全部遍历完成后
return cp;//就返回找到的
cp++;
}
return NULL;//返回NULL
}
KMP算法
思想
上面的BF算法虽然是可以解决问题的,但是每次都从子串的头部开始重新调整。对一般的字符串是没有太大的问题的,但是对于那些子串中有重复的字符串的子串来说,再从头开始寻找就有一些多次一举了。
在这种情况下,d和f明显就是不相等了,按照原来的BF算法,就得让i到b,j到a重新开始比较,但是都不相同。
但是我们的KMP算法就比较的简便了,直接找到相同的部分,然后从那往后开始比较。
要找和紧挨的子串相同的
KMP算法就是充分利用了子串中的重复字符串的特性,使得子串的字符和主串的字符不相同的时候,可以不用回到子串的0处开始寻找,
而是找从下标0开始,以j-1结尾,能否找到两个相同的字符串
使用next数组
将子串中每一个元素和主串不相同时跳转的下标做成一个数组。下标的大小就是相同字符串的长度,并规定第一个字符跳转的下标是-1,第二个字符跳转的下标是0
深度剖析next
现在我们要研究的问题是:
我们知道了j的某个字符要跳转到哪,我们就来推测一下j+1跳转到哪?
第一种情况p[j]==p[k]
第二种情况p[j]!=p[k]
如果p[j]和p[k]不相同的话,k就要往左看,是否相同了,
让k=next[k],在来判断是否相同
代码实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
void getnext(int* next, const char* sub)
{
int lensub = strlen(sub);
next[0] = -1;
next[1] = 0;
int i = 2;
int k = next[i - 1];//k的初始值就是下标为1的值
while (i < lensub)
{
if (k==-1||sub[i - 1] == sub[k])
{
next[i] = ++k;//既然相等,k向后挪一个,赋予next数组
i++;//i也要向后
}
else
{
k = next[k];
}
}
}
const char* KMP(const char* str, const char* sub)
{
//判断是否传来空指针
assert(str && sub);
int lenstr = strlen(str);
int lensub = strlen(sub);
//判断字符串的长度是否为0
assert(lenstr && lensub);
//创建next数组
int* next = (int*)malloc(sizeof(int) * lensub);
assert(next);
//赋值next数组
getnext(next, sub);
int i = 0, j = 0;
//都没有到达两个数组的末尾的时候
while (i < lenstr && j < lensub)
{
if (j == -1 || str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
//如果成功走到了子串末尾,就返回
if (j == lensub)
return str+i - j;
return NULL;
}
int main()
{
const char* str = "ababcabcdabcde";
const char* sub = "abcdf";
printf("%s\n", KMP(str, sub));
return 0;
}
对next数组的优化nextval
有时候字符串更加特殊的时候,我们就可以使用优化过的nextval数组,使得更加的简便。
比如下面的子串:
比如在最后一个a的位置和主串的元素不一样了,所以回退到倒数第二个位置去,但是倒数第二个位置的元素还是a,还是和主串不一样。
这样得一直回退到第一个元素。
所以,对于这种回退之后的元素和本元素一样的问题,我们就可以考虑对next数组进行优化。
优化的原则是这样的:
- 如果回退的位置的字符和当前位置一样,nextval数组就写那个位置的nextval
- 如果回退的位置的字符和当前位置的不一样,nextval数组就还是写那个位置的next