BF算法
BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将主串SS的第一个字符与模式串TT的第一个字符进行匹配,若相等,则继续比较SS的第二个字符和TT的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种暴力算法。
我们来看一个例子
BF算法我们很明显可以看到会出现很多多余的匹配,因为匹配错误后就要回退到开始的下一个位置,这就导致我们BF算法的时间复杂度为O(m*n)
接下来我们讲解一下BF算法优化后的算法KMP算法
KMP算法
KMP算法的原理:
1.在匹配的过程中,目标串的指针不需要不回溯,只回溯模式串的指针;
2.如果模式串和目标串前n个字符匹配成功,遇到匹配失败的字符时,模式串的指针回溯的位置由模式串的内容决定(回溯到 匹配失败位置前的模式串内容的最长公共前后缀 的长度+1),然后继续比较。
我们先来演示以下KMP算法
我们还需要讲解一个概念叫公共前后缀,最长公共前后缀
1.根据模式串的内容,生成一个遇到匹配失败的字符时模式串的指针回溯的位置的数组(next
数组)。
2.在BF算法上稍作修改,当匹配失败时,模式串的位置指针回溯next数组指示的位置。
求next数组代码如下
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 函数:计算模式串的Next数组
void getNext(const string& p, vector<int>& ne) {
int m = p.size();
ne.resize(m + 1); // 创建Next数组,大小为m + 1
ne[0] = 0; // ne[0]通常设为0,表示空串
int j = 0; // j是当前匹配的前缀末尾
// 从第2个字符开始计算Next数组
for (int i = 2; i <= m; i++) {
// 如果不匹配,回退j到上一个前缀
while (j > 0 && p[i - 1] != p[j]) {
j = ne[j]; // 回退到ne数组
}
// 如果匹配,j加1
if (p[i - 1] == p[j]) {
j++;
}
// 设置Next数组的值
ne[i] = j; // 更新ne数组
}
// 输出Next数组
cout << "Next数组为: ";
for (int i = 1; i <= m; i++) {
cout << ne[i] << " "; // 从1开始输出Next数组
}
cout << endl;
}
int main() {
string p = "ababc"; // 模式串
vector<int> ne; // 存储Next数组
getNext(p, ne); // 计算并输出Next数组
return 0;
}
代码所输出的值为0 0 1 2 0和我们画图所得的0 1 1 2 3不同,其实我们只需要把0 0 1 2 0向右移动填上-1就为-1 0 0 1 2,当时我们说过当下标为0时next数组要-1,那么0 1 1 2 3就会变为-1 0 0 1 2,此时就一样了,我们代码所求的是最长公共前缀和,其实只是不同类型的next表。
KMP算法代码如下:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
//函数:计算模式串的Next数组
void getNext(const string& p, vector<int>& ne) {
int m = p.size();
ne.resize(m + 1);
ne[0] = 0;
int j = 0;
for (int i = 2; i <= m; i++) {
while (j > 0 && p[i - 1] != p[j]) {
j = ne[j];
}
if (p[i - 1] == p[j]) {
j++;
}
ne[i] = j;
}
}
// 函数:KMP字符串匹配
void kmpMatch(const string& s, const string& p) {
int n = s.size(); // s的长度
int m = p.size(); // p的长度
vector<int> ne; // 存储Next数组
getNext(p, ne); // 计算Next数组
int j = 0; // j是模式串的指针
// 遍历文本串
for (int i = 1; i <= n; i++) {
// 当不匹配时,根据Next数组回退j的值
while (j > 0 && s[i - 1] != p[j]) {
j = ne[j];
}
// 当字符匹配时,j加1
if (s[i - 1] == p[j]) {
j++;
}
// 如果j等于模式串的长度,表示找到匹配
if (j == m) {
cout << "匹配成功,起始位置:" << i - j << endl; // 输出匹配成功的起始位置
j = ne[j]; // 根据Next数组继续查找下一个匹配
}
}
}
int main() {
string s = "aabacabababcaaab"; // 长文本
string p = "ababc"; // 模式串
kmpMatch(s, p); // 调用KMP匹配函数
return 0;
}
为了避免出现以下有多余匹配的情况我们可以把next数组优化成nextval数组(知道如何优化即可)
KMP匹配和next数组源码