KMP算法的原理和实现网上有很多大佬讲过,大多数都是使用了由PMT移位得到next数组,以便于编程。然而,本人不太喜欢引入这种没有太多具体意义的新变量,所以另外写了一种只使用PMT并且不太复杂的C++实现,以避免使用next数组影响对KMP算法的理解和记忆。
以下实现对标的问题是力扣上的28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode):
vector<int> getPMT(const string &str)
{
vector<int> pmt(str.size(), 0);
// pmt[0]必定是0,因为只有一个字符,无需考虑
for (int i = 1, j = 0; i < str.size(); ++i)
{
// 这两步和KMP一致,根本原因是找到j使得str[0:j]是与str[0:i]的后缀相匹配的最长前缀
while (j > 0 && str[i] != str[j])
{
j = pmt[j - 1];
}
if (str[i] == str[j])
{
++j;
}
// 当前j匹配成功时记录对应长度,匹配失败时计为0
pmt[i] = j;
}
return pmt;
}
int KMP(const string &str1, const string &str2)
{
vector<int> pmt = getPMT(str2);
for (int i = 0, j = 0; i < str1.size(); ++i)
{
// 1.如果在j=0处失配,无需理会,递增i直到j=0处能够匹配
// 2.如果在j!=0处失配,则j回溯到pmt[j-1]处
while (j > 0 && str1[i] != str2[j])
{
// 这是因为在str1[0:i]的PMT中,str2[0:pmt[j-1]]是与str1[0:i]的后缀相匹配的最长前缀,str2在pmt[j-1]之前的部分无需重新比较
j = pmt[j - 1];
}
// 如果能够匹配,j和i同步递增
if (str1[i] == str2[j])
{
++j;
}
// 如果str2的所有字符均匹配,直接返回
if (j == str2.size())
{
return i + 1 - j;
}
}
// 没有匹配的索引,返回-1
return -1;
}
关于PMT的定义及其与KMP算法、next数组的关系,此处不再赘述,可以参考知乎上的一个回答,个人觉得这部分讲得很好:https://www.zhihu.com/question/21923021/answer/281346746