本文将重点阐述字符串中的KMP模式匹配算法。
本文代码放置在Github处。
BF 算法
不多赘述了,给一个简要的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| vector<int> bf_search(const string &text, const string &pattern) { vector<int> result; int text_len = text.length(); int pattern_len = pattern.length(); for(int i=0; i < text_len-pattern_len+1; i++) { for(int j=0; j < pattern_len; j++) { if(text[i+j] != pattern[j]) { break; } if(j == pattern_len-1) { result.push_back(i); } } } return result; }
|
KMP 算法
本文重点在KMP算法, 但请注意, 国内网站与本课程教科书都是写的一个 next
数组, 而国外则比较常用 lps
数组, 两者的表示略有区别. 本文将使用 lps
数组.
预准备 - lps
数组
先介绍一下LPS是什么, LPS (the Longest Prefix Suffix), 就是最长的公共前缀和后缀.
与 next
数组不同, next
记录 j
指针回退的位置, 而 lps
则只是记录对应字符串的最长公共前后缀长度.
最长公共前后缀到底有什么用呢? 其实就是减少j
指针回退的次数, 原本i, j都需要回退, 而在KMP算法中, 只需要回退j, 因为我们只要找到模式串中可以与i之前的字符匹配的公共前缀, 那么就可以直接跳过i之前的字符, 减少回退次数.
也正因如此, 只需要将 j
每次赋值为 lps[j-1]
直至回退到0就行.
接下来, 我们来看下如何求出 lps
数组.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| vector<int> compute_lps(const string &pattern) { int length = 0; int i = 1; vector<int> lps(pattern.length(), 0);
while (i < pattern.length()) { if (pattern[i] == pattern[length]) { length++; lps[i] = length; i++; } else { if (length != 0) { length = lps[length - 1]; } else { lps[i] = 0; i++; } } }
return lps; }
|
主算法
算出了 lps
数组, 那么KMP算法实现就显得异常轻松了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| vector<int> kmp_search(const string &text, const string &pattern) { vector<int> lps = compute_lps(pattern); vector<int> result;
int i = 0; int j = 0; while (i < text.length()) { if (pattern[j] == text[i]) { j++; i++; }
if (j == pattern.length()) { result.push_back(i - j); j = lps[j - 1]; } else if (i < text.length() && pattern[j] != text[i]) { if (j != 0) j = lps[j - 1]; else i++; } }
return result; }
|
这样我们就实现了完整的KMP算法了,其实还是挺简单的。