KMP算法
KMP 介绍
-
KMP 算法是一种改进的字符串匹配算法;
-
算法核心是利用匹配失败后的信息,尽量减少模式串和文本串的匹配次数,以达到快速匹配的目的;
-
算法的时间复杂度为 O ( m + n ) O(m + n) O(m+n),而暴力破解的时间复杂度为 O ( m n ) O(mn) O(mn);
-
区别于暴力破解,KMP 在匹配过程中,如果出现错误的字符,会先判断文本串和模式串的是否有相同的最大相等前缀,并将指针移动到该前缀的末尾,然后继续匹配判断;这样的操作,可以有效减少匹配的次数;
-
案例一:
-
案例二:
KMP的匹配表
- 前缀:包含首字母但不包含尾字母的所有字符串;
- 后缀:包含尾字母但不包含首字母的所有字符串;
- 上图中,我们逐步获取该模式串的匹配值:
- A:0;
- AB:0;
- ABC:0;
- ABCA:1;
- ABCAB:2;
- 最后得到该模式串的匹配表:
匹配表获取
-
构建匹配表 n e x t next next 的代码分为四个步骤:
- 初始化;
- 前缀末尾 与 后缀末尾 不一致的情况;
- 前缀末尾 与 后缀末尾 一致的情况;
- 更新匹配表 n e x t next next;
-
//初始化,其中 m 是模式串的长度; vector<int> next(m); next[0] = 0; //i有代表后缀末尾,j有代表前缀末尾; //后缀要与前缀比较,故从索引 1 开始; for (int i = 1, j = 0; i < m; ++i) { //当出现不一致情况时,需要前缀末尾回退,当前j回退到索引next[j-1]处; //如果j=0,正处于模式串首字符,不需要回退,故从 j = 1开始判断; //此处应该用while循环,而不是for循环,因为回退后并不保证会出现一致情况; while (j > 0 && needle[i] != needle[j]) j = next[j - 1]; //当出现一致情况后,说明当前缀与后缀相等,故加长相等缀的长度; if (needle[i] == needle[j]) ++j; //操作完成后,最后更新匹配表信息; next[i] = j; }
匹配表使用
-
在得到模式串的匹配表后,可以通过匹配表在任何文本串中,进行字符串匹配,这是个通用的表;
-
//n为文本串haystack的长度,m为模式串needle的长度; //i为文本串的指针,j为模式串的指针; for (int i = 0, j = 0; i < n; ++i) { //如果当前字符不匹配,则将j移动到最长相等前缀的末尾处; while (j > 0 && haystack[i] != needle[j]) j = next[j - 1]; //如果字符匹配,则继续匹配模式串的下一个字符; if (haystack[i] == needle[j]) ++j; //如果j到达模式串的末尾,说明找到匹配字符串,返回; if (j == m) return i - m + 1; } return -1;
案例
- 描述:实现 s t r S t r ( ) strStr() strStr() 函数;给两个字符串 h a y s t a c k haystack haystack 和 n e e d l e needle needle,请在 h a y s t a c k haystack haystack 字符串中找出 n e e d l e needle needle 字符串出现的第一个位置(下标从 0 开始);如果不存在,则返回 -1;如果 n e e d l e needle needle 是空字符串,应当返回 0; 0 ≤ h a y s t a c k . l e n g t h , n e e d l e . l e n g t h ≤ 5 ∗ 1 0 4 0 \leq haystack.length,\ needle.length \leq 5 * 10^4 0≤haystack.length, needle.length≤5∗104; h a y s t a c k haystack haystack 和 n e e d l e needle needle 仅由小写英文字母组成;
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
if (!m) return 0;
vector<int> next(m);
next[0] = 0;
for (int i = 1, j = 0; i < m; ++i) {
while (j > 0 && needle[i] != needle[j]) j = next[j - 1];
if (needle[i] == needle[j]) ++j;
next[i] = j;
}
for (int i = 0, j = 0; i < n; ++i) {
while (j > 0 && haystack[i] != needle[j]) j = next[j - 1];
if (haystack[i] == needle[j]) ++j;
if (j == m) return i - m + 1;
}
return -1;
}
};