目录
0.前言
KMP算法是在字符串中寻找字串的算法,时间复杂度为O(n)。
KMP算法中有两个关键因素:
- next数组
- 匹配机制
1. 视频理解KMP的流程
由于想把最好的资源分享给大家(绝对不是作图水平太低!,读者们可以在下面的视频中先行了解KMP算法,这也是作者最先了解KMP思想的视频:
如果大家看完视频后还在看这篇文章,那么下面就是对于KMP算法的具体实现。
2.代码实现
p1建议使用“cbcbc”这个字符串来带入理解。
p2建议使用“cbc”这个字符串来带入理解。
开始之前,我们需要知道字符串前后缀子串:
像这样,如果是奇数,那么前3个字符称为字符串的前缀,后面的CBA则是后缀。
ne数组的原理就是从第二个字符开始往后找。
字符串AB中,前缀A不等于前缀B,于是B在next数组中存的是0;
字符串ABC中,前缀A和 后缀C不同,于是C在next数组中存的是0;
以此类推~~~
char* my_strstr(const char* p1,const char* p2)
{
//在 s 字符串中寻找 p 字符串
char* s = p1 - 1; char* p = p2 - 1;
//next数组 ne
int* ne = (int*)malloc(strlen(p2) * 4);
//因为首字符没有前后缀子串
ne[1] = 0;
//next数组的创建
for (int i = 2, j = 0; p[i]; i++)
{
// j 代表了 目前正在匹配的字符的前一个字符 的下标。
while (j && p[j + 1] != p[i])
{
//如果匹配失败,那么 j 作为下标将会回到 ne[j] 的位置
j = ne[j];
}
//匹配成功,证明有相同的前后缀字串,j代表了相同的子串长度
if (p[i] == p[j + 1])
{
j++;
}
//next数组中存放i位置时,相同的前后缀子串长度
ne[i] = j;
}
//寻找字串
for (int i = 1, j = 0; s[i]; i++)
{
// j 代表了模板串的 已经匹配成功的 长度
//匹配不成功就回撤继续匹配
while (j && p[j + 1] != s[i])
{
j = ne[j];
}
//匹配成功,已经匹配成功的长度加1
if (p[j + 1] == s[i])
{
j++;
}
//如果模板串已经匹配完最后一个字符,那么停止匹配,返回子串首字符地址
if (j == strlen(p2))
{
// i - strlen(p2) 子串首字符距离p2的长度
return p1 + i - strlen(p2);
}
}
return NULL;
}
其实写在函数里多少有点影响KMP算法的功能,因为这个算法可以找到多处子串的起始位置。
所以可以考虑改造一下或则写在main函数中。
写在main函数中时,通常用字符数组来存字符串,这里说一下,字符串最好从下标为1的地址开始存。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000100;
//next数组
int ne[N];
//在p中寻找子串s
char s[N],p[N];
//字符串s的长度为m,字符串p的长度为n
int m,n;
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
//创建next数组
for(int j = 0, i = 2; i <= n; i++)
{
while(j && p[j + 1] != p[i])
{
j = ne[j];
}
if(p[i] == p[j+1])
{
j++;
}
ne[i] = j;
}
//开始匹配
for(int i = 1,j = 0; i <= m; i++)
{
while(j && s[i] != p[j + 1])
{
j = ne[j];
}
if(s[i] == p[j + 1])
{
j++;
}
if(j == n)
{
//返回子串下标
printf("%d ",i - n);
j = ne[j];
}
}
return 0;
}
3.结束语
KMP算法是比较难理解的一个算法,要多琢磨,建议数组储存数据时从下标1开始储存,这样会方便很多,本代码中让p1和p2减一也是为了实现 储存数据时从下标1开始储存。
另外,能用KMP算法解决的问题,同样也可以使用字符串哈希算法解决。有兴趣可以去了解一下。