-------------------------------------------------预备知识-----------------------------------------------
1、字符串前缀
对于字符串str = aabbcd
,其前缀为{a, aa, aab, aabb, aabbc}
2、字符串后缀
同样对于上述字符串,其后缀为{d,cd,bcd, bbcd, abbcd}
3、字符串最长相等前后缀
对于字符串str = aabbcd
,我们从上面可以知道,其没有相同的前缀和后缀,故对于该字符串,它的最长相等前后缀的值为0;
而对于字符串str = aabaa
,其前缀为{a, aa, aab, aaba}
,其后缀为{a, aa, baa, abaa}
,可以知道,他有相同的前后缀a
和aa
。所以该字符串的最长相等前后缀的值为 2。
-------------------------------------------------正文开始--------------------------------------------------
问题描述:
给定一个源字符串source
,以及一个模式串pattern
,求模式串在源字符串出现的第一个位置。如何源字符串中不包含模式串,则返回 -1。
例如:
源字符串为string source = "aabaabaaf"
模式串为string pattern = "aabaaf"
则应该返回源字符串包含模式串的第一个位置 3
对于上图可以知道,当我们匹配到第六个字符的时候,发现不匹配,这时候就需要回退指向模式串的ppointer指针,然后进行重新匹配。至于指针应该回退到什么位置,看下面描述。
4、next[]数组求解
KMP算法的核心就是求next[ ]数组问题。
其中next[]数组存的就是当发生不匹配的时候,ppointer指针应该回退的下标位置。这时候,就用到了上述提到的最长相等前后缀。
next[i]表示的是在下标 i
之前的字符串,其最长相等前后缀的长度。
如,对于字符串string source = "aabaabaaf"
next[0] = 0 因为在0下标之前没有字符串
next[1] = 0 因为'a'
不存在前后缀
next[2] = 1 最长相等前后缀为'a'
next[3] = 0 字符串为'aab'
无相等前后缀
同理可以知道
next[4] = 1
next[5] = 2
next[6] = 3
next[7] = 4
next[8] = 5
next[]数组求解过程:
- 1、初始化
next[0] = 0; j = 0;
(j
表示前缀末尾) - 2、当前缀末尾和后缀末尾不相等时,回退
j
,j = next[j-1]
- 3、当前缀末尾和后缀末尾相等时,前缀末尾
j
往前移动 - 4、更新
next
数组
下面进行代码的实现:
(下面的i
表示前缀末尾,j
表示后缀末尾)
void getNext(vector<int>&next, const string& s)
{
int n = next.size();
//初始化j和next数组
int j = 0; //代表前缀末尾
next[0] = 0;
//i从1开始是因为i表示的是后缀末尾,而要存在后缀末尾,则i不能为0,因为i为0的话不存在后缀
for(int i = 1; i < n; i++)
{
//当前缀末尾和后缀末尾不相等时,回退j
while(j > 0 && s[i] != s[j])
{
j = next[j-1];
}
//当前缀末尾和后缀末尾相等时,前缀末尾前移
if(s[i] == s[j])
{
j++;
}
//更新next数组
next[i] = j;
}
}
5、KMP字符串匹配
在求解完next[]数组之后,我们可以根据next数组来确定当ppointer与spointer不匹配时,ppointer应该回退的位置了。
然后求解上述问题的过程如下:
- 1、用
i
来表示源字符串的位置,j
表示模式串的位置 - 2、若源字符与模式串字符不匹配,回退
j
- 3、若源字符串与模式串字符匹配,
j
前移 - 4、判断
j
是否等于模式串长度,若是,则匹配结束,否则i++
,继续匹配
代码如下:
class sulotion
{
public:
void getNext(vector<int>&next, const string& s)
{
int n = next.size();
//初始化j和next数组
int j = 0; //代表前缀末尾
next[0] = 0;
for(int i = 1; i < n; i++)
{
//当前缀末尾和后缀末尾不相等时,回退j
while(j > 0 && s[i] != s[j])
{
j = next[j-1];
}
//当前缀末尾和后缀末尾相等时,前缀末尾前移
if(s[i] == s[j])
{
j++;
}
//更新next数组
next[i] = j;
}
}
int KMP(const string& source, const string& pattern)
{
int n = pattern.size();
vector<int>next(n);
getNext(next, pattern);
int j = 0, i = 0;
for(; i < source.size(); i++)
{
while(j > 0 && source[i] != pattern[j])
{
j = next[j-1];
}
if(source[i] == pattern[j])
{
j++;
}
if(j == n)
{
return i - j + 1;
}
}
return -1;
}
};