KMP算法
Posted TangguTae
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法相关的知识,希望对你有一定的参考价值。
场景:在字符串去寻找一个特定的子串
28. 实现 strStr() - 力扣(LeetCode)
暴力匹配(BF)方法
暴力匹配的方法不难想到,也很容易实现。本质上说就是双指针的方式,逐个字符进行比较。
KMP算法
上述暴力的方法给出一旦某个字符匹配失败,子串的索引会回到一开始,重头再来。这样没有利用到以前的遍历过程中的信息。
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的(避免重复的比较),是一种典型的空间换时间的方法。
用KMP算法的过程如上图, 可以看到,他利用了之前比较的信息,当子串最后一个字符不匹配的时候,首先主串的指针不需要回到下标为1的位置,而是原来的位置保持不变。子串现在寻找新的开始位置。
这样就可以减少比较次数。
那么如何去记录应该从哪一个字母开始呢?
KMP算法的核心:next数组
next数组的作用:找到子串回退的下标位置。
用刚才的例子对应的子串为
当前位置 i 的字符回退下标是:寻找以0开始的子串与 i-1 结束的子串相等最大长度,就为当前位置的回退下标。具体过程如下图所示:
现在我们就知道上面的例子为什么会回退到字符 c 这里,也就是子字符串下标为2这里。
举个复杂一点的例子:子字符串为 abcabcabcabcdabcde
如何从前一个字符的回退下标 推导出 当前字符的回退值?
如图所示:
上图给出的是当 subStr[k] == subStr[i-1] 的时候,如果不相等即 subStr[k] != subStr[i-1]时
next数组隐含的特性:如果next[i+1] > next[i], 那么next[i+1] == next[i] + 1; 不存在超越大于1的情况,如果有,那一定是错的。
代码实现:
int KMP(const string& str,const string& subStr)
if (subStr.empty())
return -1;
if (str.size() < subStr.size())
return -1;
vector<int> next(subStr.size());//next数组,存储回退值
next[0] = -1;
for (int i = 1; i < subStr.size(); i++)
int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
pre = next[pre];
if (pre == -1)//没找到,则回退值为0,代表需要重头开始
next[i] = 0;
else
next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
//开始匹配
int point_str = 0;//主串的指针
int point_sub = 0;//子串的指针
while (point_str < str.size() && point_sub < subStr.size())
if (point_sub == 0 && str[point_str] != subStr[point_sub])
point_str++;
else
if (str[point_str] == subStr[point_sub])
point_str++;
point_sub++;
else
point_sub = next[point_sub];
if (point_sub == subStr.size())//子串的指针走到最后,说明子串找到了
return point_str - point_sub;
return -1;
next数组的优化
举个例子
具体比较过程
subStr[ next[i] ] = subStr[i]时候,会存在很多重复性的比较。所以为了避免重复性比较,引入nextval数组。
nextval数组就是如果出现上述情况,让回退的值一次到位,即当subStr[ next[i] ] == subStr[i]时候,nextval[i] = nextval[ next[i] ]; 否则还是和之前相同,nextval[i] = next[i];
具体实现
int KMP(const string& str,const string& subStr)
if (subStr.empty())
return -1;
if (str.size() < subStr.size())
return -1;
vector<int> next(subStr.size());//next数组,存储回退值
next[0] = -1;
for (int i = 1; i < subStr.size(); i++)
int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
pre = next[pre];
if (pre == -1)//没找到,则回退值为0,代表需要重头开始
next[i] = 0;
else
next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
//计算nextval数组
vector<int> nextval(subStr.size());
nextval[0] = -1;
for (int i = 1; i < subStr.size(); i++)
subStr[i] == subStr[next[i]] ? nextval[i] = nextval[next[i]] : nextval[i] = next[i];
//开始匹配
int point_str = 0;
int point_sub = 0;
while (point_str < str.size() && point_sub < subStr.size())
if (point_sub == 0 && str[point_str] != subStr[point_sub])
point_str++;
else
if (str[point_str] == subStr[point_sub])
point_str++;
point_sub++;
else
point_sub = nextval[point_sub] >= 0 ? nextval[point_sub] : 0;//如果为-1表示在第一个字符这
if (point_sub == subStr.size())
return point_str - point_sub;
return -1;
以上是关于KMP算法的主要内容,如果未能解决你的问题,请参考以下文章