BF算法(暴力解法)
int BF(std::string& s, std::string& t)
{
int i = 0, j = 0;
int size1 = s.size(); // 比如主串长度为m
int size2 = t.size(); // 比如子串长度为n
while (i < size1 && j < size2) // 时间复杂度O(m * n)
{
if (s[i] == t[j]) // 相等的情况
{
i++; j++; // 两个字符串都向后比较
}
else // 不相等的情况
{
i = i - j + 1; // 主串递增一个字符
j = 0; // 子串从第一个字符开始
}
}
if (j == size)
return i - j;
return 0;
}
KMP
KMP算法则是在BF算法基础上改进,先分析一下BF算法有哪些不足
这是BF算法中进行比较的过程。
当【情况①】 f和y不相等,j被重置为0,i设置为1.然后再依次进行比较。
其实仔细观察可以发现,"a b d e"上下都相等,然后这四个字符彼此又不同.所以,【情况②、③、④】根本不需要进行匹配比较,这几次比较完全就是浪费性能.应该直接从【情况⑤】进行比较。
解决上面这种情况就是当不相等时, i不要设置 i = i - j + 1,i保持原位置即可
再看下面这幅图
"a b b a",这四个字符中,前面和后面两个a相等,长度为2
那么上面这幅图,不相等,i和j应该如何设置?
因为字符f前面有'a',字符y前面也有'a',我们将j=1,从子串'b'开始和主串'f'进行比较.
设置j的下标其实和子串中相等前后缀字符串的长度有关
相等前后缀字符串
就上图来说,就是j下标之前的字符串. 找出前面和后面相等的字符串
相等前后缀字符串的长度为1,那么j回溯设置的下标就为1,也就将字符b和字符f进行比较
每次回溯j应该设置什么下标,这就需要有个地方存储,所以就需要KMP代码中的next数组
#include <memory>
#include <string>
using std::unique_ptr;
using std::string;
int* getNext(string str)
{
int size = str.size();
int* next = new int[size];
int j = 0; // 遍历子串
int k = -1; // 表示公共前后缀长度
next[j] = k;
while (j < size - 1)
{
if (k == -1 || str[k] == str[j])
{
j++;
k++;
next[j] = k;
}
else
k = next[k]; // 回溯,继续找最长的公共前后缀
}
return next;
}
int KMP(std::string s, std::string t)
{
int i = 0, j = 0;
int size1 = s.size(), size2 = t.size();
unique_ptr<int> next(getNext(t));
while (i < size1 && j < size2)
{
if (j == -1 || s[i] == t[j]) // j == -1表示首字符没有匹配成功
{
i++; j++;
}
else
{
// KMP核心是不回退i,只回退j值
j = next.get()[j]; // 如果首字母匹配失败,这里j == -1
}
}
delete next;
if (j == size2)
return i - j;
return -1;
}
上述代码的next数组有个问题,那就是当子串为 "abababc"
根据上面没有优化的代码, next数组存储的应该为: [-1, 0, 0, 1, 0, 1, 2]
当获取next[2]的时候,获取的值是0, 将j设置为0,然后子串下标0还是个字符'a'.
还有获取next[3]和next[5],获取的值是1. 子串下标1还是字符'b',所以上述代码需要加个判断优化一下
int* getNext(std::string& t)
{
int size = t.size();
int* next = new int[size];
int j = 0; // 用来遍历子串
int k = -1; // 公共前后缀的长度
next[j] = k;
while (j < size - 1)
{
if (k == -1 || t[j] == t[k])
{
k++; j++;
if (t[k] == t[j])
// KMP算法优化
next[j] = next[k];
else
next[j] = k;
}
else
k = next[k]; // 做k值回溯,继续找最长的公共前后缀
}
return next;
}