一直以来都对字符串匹配 kmp 算法的理解模棱两可,今天经过一个多小时的钻研算是有了比较深入的理解,顺便在这里记录一下。
这里主要记录一下 kmp 算法中 next 数组的求法。
简单地说,对于模式串的某一位置j
,next[j]
的值是该模式串从下标0
到j - 1
的子串最大相等前缀与后缀数,下面举个例子加以说明。模式串pattern
及其对应的下标如下图所示:
![d9a51cfb04636f744a7d0770d61389d0.png](https://i-blog.csdnimg.cn/blog_migrate/4b3ac14fbd4dee86697d9c7dbe5fe03a.png)
位置 0
上的元素 a
前面没有子串,因此这里我们令 next[0] = -1
;
位置 1
上的元素 b
,它前面的字符串为 a,字符串 a 没有最大相等前缀和后缀(注意:最大相等前缀后缀不包括自身),因此 next[1] = 0
;
位置 2
上元素的 a
, 它前面的字符串为 ab
,字符串 ab
没有最大相等前缀和后缀,因此next[2] = 0
;
位置3
上元素的a
, 它前面的字符串为aba
,字符串aba
的最大相等前缀和后缀为a
,因此next[3] = 1
;
位置4
上元素的b
, 它前面的字符串为abaa
,字符串abaa
的最大相等前缀和后缀为a
,因此next[4] = 1
;
位置 5
上的元素 c
, 它前面的字符串为 abaab
,字符串 abaab
的最大相等前缀和后缀为 ab
,因此 next[5] = 2
;
位置 6
上的 元素a
, 它前面的字符串为 abaabc
,字符串 abaabc
的没有最大相等前缀和后缀,因此 next[6] = 0
;
位置 7
上的 元素b
, 它前面的字符串为 abaabca
,字符串 abaabca
的最大相等前缀和后缀为 a
,因此 next[7] = 1
;
位置 8
上的 元素a
, 它前面的字符串为 abaabcab
,字符串 abaabcab
的最大相等前缀和后缀为 ab
,因此 next[8] = 2
。
通过以上步骤,我们便可得到整个next
数组的值,其余pattern
的对应关系如下:
![3f288416b896fe6f9459ef1ebb3cdaa1.png](https://i-blog.csdnimg.cn/blog_migrate/1bf0ac52227955c6ac0478a756906e29.png)
那么我怎么通过代码来实现对next
数组的求解呢,这里给出next
的递推关系。对于模式串的位置j
,有next[j] = k
,例如取j = 4
,则有next[4] = 1
;则对于模式串的位置j + 1
,有以下两种情况:
若 p[k] == p[j]
,即p[1] == p[4]
,则有next[j + 1] = next[j] + 1
;
若 p[k] != p[j]
,则令k = next[k]
,若 p[k] == p[j]
,next[j + 1] = k + 1
,否则重复此过程。
具体代码实现如下:
#include <iostream>
#include <string>
using namespace std;
void getNext(const string& p,int next[])
{
int len = (int)p.size();
next[0] = -1;
int k = -1;
int j = 0;
while(j < len - 1)
{
if(k == -1 || p[j] == p[k])
{
++j;
++k;
next[j] = k;
}
else
{
k = next[k];
}
}
}
int kmp(const string& s, const string& pattern)
{
int n = (int)s.size();
int ans = -1;
int i = 0;
int j = 0;
int patternLen = (int)pattern.size();
int next[patternLen] = {0};
getNext(pattern,next);
while(i < n)
{
if(j == -1 || s[i] == pattern[j])
{
++i;++j;
}
else
{
j = next[j];
}
if(j == patternLen)
{
ans = i - patternLen;
break;
}
}
return ans;
}
int main()
{
string s = "abbbadabaabcabadba";
string pattern = "abaabcaba";
cout << kmp(s,pattern) << endl;
return 0;
}