一、算法思路:
KMP模式匹配算法让主串不发生没必要的回溯,即主串的索引值只做递增操作。如果主串的索引值不发生回溯,那么就需要子串索引值的回溯变化。
子串索引值要以一种怎样的形式进行变化呢?子串索引值的变化方式只和子串自身有关系,而且索引值的变化取决于当前字符之前的串的前后缀的相似度。
我们以next数组表示串的相似度。next数组的本质就是寻找子串中相同前后缀的长度,以此来表达匹配过程中需要跳过的字符个数。即根据已经掌握的信息来避免重复运算。
二、参考链接:
三、算法代码:
/* KMP模式匹配算法 */
short kmpSearch(std::string mainStr, std::string subStr) {
auto retValue = -1;
const std::vector<short> nextVec = getStrNextVec(subStr); // 获取子串的next数组
short mainIdx = 0, subIdx = 0; // 主串、子串的索引下标
// 这个过程实际和查找next数组的过程有点类似
while (mainIdx < mainStr.length()) {
if (mainStr.at(mainIdx) == subStr.at(subIdx)) {
// 字符匹配,索引继续往后偏移
mainIdx += 1;
subIdx += 1;
}
else {
if (0 == subIdx) {
// 子串的第一个字符就不匹配,直接偏移主串的索引
mainIdx += 1;
}
else {
// 根据next数组,跳过子串前面的一些字符,不用进行匹配
subIdx = nextVec.at(subIdx - 1);
}
}
if (subIdx == subStr.length()) {
// 子串的索引值等于子串长度的时候,那么就匹配成功了
retValue = mainIdx - subIdx;
break;
}
}
return retValue;
}
/* KMP算法,获取next数组 */
std::vector<short> getStrNextVec(std::string subStr) {
std::vector<short> nextVec(0);
int prefixIdxLen = 0; // 当前子串前缀对应的索引值,同时也表示子串相同前后缀的最大长度
int suffixIdx = 1; // 当前子串后缀对应的索引值
while (suffixIdx < subStr.length()) {
if (subStr.at(prefixIdxLen) == subStr.at(suffixIdx)) {
// 两个字符相等,最大相同前后缀长度加一,继续往后查找
prefixIdxLen += 1;
suffixIdx += 1;
nextVec.push_back(prefixIdxLen);
}
else {
// 两个字符不相等,当前子串的后缀与当前子串的前缀从头开始对比
// 但是仍然可以利用已知信息,确定当前子串后缀需要从哪里开始与当前的子串前缀继续对比
if (0 != prefixIdxLen) {
// 前一个字符对应的子串的相同前后缀不为0,那么当前字符在对比的时候是可以跳过子串的部分字符的
prefixIdxLen = nextVec.at(prefixIdxLen - 1);
}
else {
// 前一个字符对应的子串的相同前后缀为0,而且当前字符也不相等,那么直接为0,继续查找下一个字符的next值
nextVec.push_back(0);
suffixIdx += 1;
}
}
}
return nextVec;
}