什么是KMP
要做一个东西我们先要理解一个东西,KMP是什么,就是我的标题,字符串匹配。就这样讲可能不好理解,这里我们先抛出一个题目,下文就以这个题讲讲跟着理解一下。
KMP有什么好处
上题大多数人刚刚入门的人肯定会想到,暴力求解,下面也会提一下,但使用KMP的好处就是能够消除了主串指针的回溯,从而使算法效率有了某种程度的提高。(了解一下就好)好了,进正题。
暴力解法(不是重点,带过)
思路:假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:
如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
int ViolentMatch(char* s, char* p)
{
int sLen = strlen(s);
int pLen = strlen(p);
int i = 0;
int j = 0;
while (i < sLen && j < pLen)
{
if (s[i] == p[j])
{
//如果当前字符匹配成功(即S[i] == P[j]),则i++,j++
i++;
j++;
}
else
{
//如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0
i = i - j + 1;
j = 0;
}
}
//匹配成功,返回模式串p在文本串s中的位置,否则返回-1
if (j == pLen)
return i - j;
else
return -1;
}
KMP算法
上题kmp算法如何做到在aabaabaah
字符串中找到这个与模式串aabaah
相匹配的子串的呢,就需要另一个概念,前缀表。
前缀表
我们模式串aabaah在匹配时出现了aabaa匹配,到h不和b匹配了,那么从h的前面有一个模式串的子串aabaa,后缀是aa,找到与其相等的前缀也是aa,所以下一次匹配就从b开始。
这样我们在匹配时遇到不匹配的字符时只需要找它前面的最长相等前后缀。
先来理清另一个概念,
**
前缀是包含首字母,不包含尾字母的所有子串都是前缀。
后缀只包含尾字母,不包含首字母的所有子串都是后缀。
用aabaah举例。
前缀 | 后缀 |
---|---|
a | h |
aa | ah |
aab | aah |
aaba | baah |
aabaa | abaah |
** **
上图可以看到匹配时,遇到h不匹配了,如何看前面子串最长相等前后缀,就是h的前面一个a,长度是2,意义在于这个后缀的前面有一个相等长度为2的前缀,那么在这个后缀后面匹配不相等了就要在这个前缀的后面重新开始找匹配,而与其相等前缀的后面的那个下标,也就是b的下表,正好是2。
一般情况都会做一个操作,右移或者全部减一使得起始位为-1,这里全部减一。两种不同的kmp算法实现方式,不做此操作也可以,不涉及原理问题。
代码实现
void kmp(int* next, const string& s){
next[0] = -1;
int j = -1;
for(int i = 1; i < s.size(); i++){
while (j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if (s[i] == s[j + 1]) {
j++;
}
next[i] = j;
}
}
文章参考:代码随想录,kmp算法