更多文章可以在本人的个人小站:https://kaiserwilheim.github.io 查看。
转载请注明出处。
简介
KMP算法,全称为 Knuth-Morris-Pratt 算法,是由 Knuth, Morris 和 Pratt 这三个人创造的算法,可以在 O ( n + m ) O(n+m) O(n+m) 的时间内使用 O ( n ) O(n) O(n) 的空间完成如下的任务:
给定一个字符串 S S S 和一个模式串 T T T,求出 S S S 在 T T T 中所有出现的位置。
其中 ∣ S ∣ = n |S| = n ∣S∣=n, ∣ T ∣ = m |T| = m ∣T∣=m。
KMP算法主要依赖的是 “Next函数” 这个东西。
Next函数
Next函数,有时候也被称作 “前缀函数”,是KMP算法的核心部分。
我们以一个数组 π \pi π 来表示它。
其旨在求得任意一个前缀的border长度。
什么是border?
border指的是一个字符串内,真前缀和真后缀相等的那一部分。
这样的真前缀和真后缀可能有很多种,我们需要找的是最长的那一组。
真前缀和真后缀说的是前缀和后缀中除去字符串本身之后剩下的部分。
如何求得border?
朴素算法
我们显然可以暴力扫,最终的复杂度是 O ( n 3 ) O(n^3) O(n3) 的。
懒得写了,直接搬了OI-Wiki的代码。
// C++ Version
vector<int> prefix_function(string s)
{
int n = ( int )s.length();
vector<int> pi(n);
for(int i = 1; i < n; i++)
for(int j = i; j >= 0; j--)
if(s.substr(0, j) == s.substr(i - j + 1, j))
{
pi[i] = j;
break;
}
return pi;
}
# Python Version
def prefix_function(s):
n = len(s)
pi = [0] * n
for i in range(1, n):
for j in range(i, -1, -1):
if s[0 : j] == s[i - j + 1 : i + 1]:
pi[i] = j
break
return pi
优化
我们会发现,相邻的两个函数值最多会增加1。
也就是说,当我们移动到下一个位置时,Next函数的值要么增加一,要么维持不变,要么减少。
此时改进的算法如下:
// C++ Version
vector<int> prefix_function(string s)
{
int n = ( int )s.length();
vector<int> pi(n);
for(int i = 1; i < n; i++)
for(int j = pi[i - 1] + 1; j >= 0; j--) // improved: j=i => j=pi[i-1]+1
if(s.substr(0, j) == s.substr(i - j + 1, j))
{
pi[i] = j;
break;
}
return pi;
}
# Python Version
def prefix_function(s):
n = len(s)
pi = [0] * n
for