next[i]
next[i]记录字符串str[0,i]相等的最长前后缀的前缀的最后一位下标
例如有一字符串 str="abab"
字符串前缀有 “a”,“ab”,"aba"
字符串后缀又 “b”,“ab”,"bab"
那么str的相等的最长前后缀分为别 “ab”,“ab”,前缀"ab"最后一个字符’b’的下标为1,所以字符串str的next[3]=1。
next[i]计算需要排除自身,即前、后缀不能选自己。
-
变量j的含义
我们引入变量j的主要目的是在计算next[i]时,使用next[i-1]的成果,找个变量j=next[i-1],那么next[i]的值为j+1,即迭代公式
next[ i ] = next[ i - 1 ] +1
但是这个迭代公式使用有一个条件
str[ i ] == str[ j + 1 ]
也就是说变量 j 的作用是在计算next [ i ] 的时候,存储str [ 0, i - 1 ]的最长相等前后缀的最后一个字符的下边,即 next[ i - 1 ],即j = next [ i - 1 ];
在计算next [2 ]的时候,为什么j初始化赋值为-1呢?
根据上面说的 j = next[ i - 1 ]= next[ 1 ],对于str[ 0, 1 ] = “ab”,不难计算next[ 1 ] = -1;
注意,对于任何字符串都有next[ 0 ] = -1
得出一个结论,
当str[ i ] == str[ j + 1 ]时,next[ i ] = j+1; -
str[ i ] != str[ j + 1 ]时应该如何处理?
这时,只能把j往前移,最优移动步骤为一直赋值
j = next[ j ],直到str[ i ] == str [ j + 1 ]成立
一直退到j=-1,而j+1=0,即字符串str 的头部,无法再退了,意味着只能重头开始匹配。
此时如果j = -1, str[ i ] == str[ j + 1 ]仍然不成立的话,
只能宣布next[ i ] = -1,即str[ 0, i ] 的最长相等前后缀为空
next数组快速计算代码实现
vector<int>getNext(const string& str) {
vector<int>next(str.size());
next[0] = -1;
for (int i = 1, j = -1; i < str.size(); i++) {
while (j != -1 && str[i] != str[j + 1]) {
//如果不能匹配,只能退j
j = next[j];
}
if (str[i] == str[j + 1]) {
//如果成功匹配了一个字符,则next[i] = j+1
//否则next[i]=-1
j++;
}
next[i] = j;
}return next;
}
KMP算法
例如有strOne = “ababaabc”, strTwo = “abaab”,判断strOne是否包含子串strTwo.
变量i的作用是指示strOne字符串下标,变量j的作用是表示已经连续匹配成功的strTwo下标
j后移的含义:strTwo成功连续匹配到一个字符,j后移
j往前退,j = next[ j ]
j到达字符串strTwo的尾端,即匹配成功
如果j一直退还不满足strTwo[ j + 1]==strOne[ i ],也就是j退到strTwo的起始位置都不满足条件,此时只能后移i,期待后面能匹配成功。
KMP算法的代码实现
//判断字符串strOne是否包含子串strTwo
bool kmp(const string& strOne, const string& strTwo) {
//先计算字符串strTwo的next数组
vector<int>strTwoNext = getNext(strTwo);
for (int i = 0, j = -1; i < strOne.size(); i++) {
while (j != -1 && strOne[i] != strTwo[j + 1]) {
//如果不匹配只能一直退j
j = strTwoNext[j];
}
if (strOne[i] == strTwo[j + 1]) {
//匹配成功了一个字符,则j后移
j++;
}
if (j == strTwo.size() - 1) {
//j已经到达了strTwo的尾端,说明匹配已经完成
return true;
}
}return false;
}