1.解决什么问题
kmp解决在字符串s中查找某个字符串p是否出现,也就是字符串匹配问题。
2.解决方法
假设s的长度为n,p的长度为m。
2.1 暴力匹配
枚举s中每个长度为m的子串,然后一位一位的比较这些子串和p一不一样。总的时间复杂度为O(nm)。
2.2 哈希
先预处理出字符串p、s的哈希值,再判断s中每个长度为m的子串的哈希值和p是否一样。但很有可能会发生哈希冲突。总的时间复杂度为O(n+m)。
2.3 KMP
简介
在KMP算法中,对于s中每个位置i,我们要找到最大的 j 满足s[i - j + 1]...s[i] 和 p[1]...p[j]是相同的,既在s中以s[i]结尾的后缀和p中以p[j]结尾的前缀是相同的。
i 向后移有两种情况:
1. j 不等于 m,s[ i + 1 ] 和 p[ j + 1 ]相同。这种情况 j 也向右移动一位
2. s[ i + 1 ] 和 p[ j + 1 ]不同或 j 等于 m 。这种情况只要让 j 向前回到满足串 s[ i - k + 1 ]…s[ i ] 和 p[ 1 ]… p[ k ]完全相等,且k的值最大的位置,然后继续判断。如果s[ i + 1 ] 和 p[ j + 1 ] 仍然不相同,那就继续向前回退,知道s[ i + 1 ] 和 p[ j + 1 ] 相同或 j = 0。
为什么要找k最大的呢?
满足条件的 k 可能有很多种,然而对于一个固定的 i ,要使得 j 最大,j 需要回退到 k 的位置
,所以 k 也需要最大。
如何快速求k?
k 其实只和 p 有关,前面提过 s[ i - k + 1 ] ... s[ i ] 和 p[ 1 ] ... p[ j ] 相同。所以我们要求的是最大的 k 满足 k < j ,使得p[ 1 ] … p[ k ]和 p[ j - k + 1].… p[ i ]完全相同。也就是p当中长度为 k 的前缀和 p 当中以 p[ j ] 为结尾,长度为 k 的后缀相同。
用 next 数组来维护每个 j 对应的 k
假设第 j 位的 next[ j ] = k 已知,要求第 j + 1 位的next[ j + 1 ],分为两种情况:
1. p[ k + 1 ] 和 p[ j + 1 ] 一样,那就再往后匹配一位,next[ j + 1 ] = k + 1;
2. p[ k + 1 ] 和 p[ j + 1 ] 不一样,那就回退到 next[ j ],如果下一位还不一样,还是继续回退。直到相同或者 j 等于0.
代码模板:
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
/*求模式串的Next数组:*/
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}