在看KMP算法之前,我们先看看基础的BF算法。BF(Brute Force)算法,也称蛮力匹配算法,是一种很基础的字符串匹配算法,它的思想也是非常简单直观,将目标串(主串)和模式串(子串)的字符逐一进行匹配,若匹配失败则回溯,重新开始匹配,直到匹配成功或遍历完主串。
算法步骤
目标串(主串) S = "heloohello", 模式串(子串) T = "hello"。我们要从主串 S 中找到子串 T 的位置。BF 算法按照以下步骤进行。
- 主串 S 第一位开始, S 与 T 前三个字母都匹配成功。但 S 第四个字母是 'o',而 T 的是 'l'。匹配失败,子串重新开始匹配。
- 主串 S 第二位开始,主串 S 字母是'e',要匹配的 T 首字母是'h',匹配失败,子串重新开始匹配。
- 主串 S 第三位开始。主串 S 字母是'l',要匹配的 T 首字母是'h',匹配失败,子串重新开始匹配。
- 主串 S 第四位开始,主串 S 字母是'o',要匹配的 T 首字母是'h',匹配失败,子串重新开始匹配。
- 主串 S 第五位开始,主串 S 字母是'o',要匹配的 T 首字母是'h',匹配失败,子串重新开始匹配。
- 主串 S 第六位开始。 S 与 T 所有字母都匹配成功。
算法思想
经过上面的步骤,相信大家应该都对BF算法有了清晰的认识,BF算法从主串的首字符开始,与匹配串(子串)进行匹配。当匹配失败时,子串回溯到初始位置,主串从本次匹配的初始位置后移一位,再次进行匹配,直到匹配成功或者主串到末尾截止。
代码
可以看出BF算法由一个循环进行遍历,代码如下:
#include <iostream>
using namespace std;
int BF(string S, string T) {
// i用于遍历主串S
int i = 0;
// j用于遍历匹配串T
int j = 0;
while (i < S.size() && j < T.size()) {
// 匹配成功则继续向下一个字符进行匹配
if (S[i] == T[j]) {
++i;
++j;
}
// 匹配失败进行回溯
else {
// i从本次匹配的初始位置后移一位
i = i - j + 1;
// j回溯到初始位置
j = 0;
}
}
// 匹配成功
if (j == T.size()) {
// 返回子串在主串匹配成功的第一个字符的索引位置
return i - j;
}
// 匹配失败
else {
return -1;
}
}
int main() {
string S = "heloohello";
string T = "hello";
int index = BF(S, T);
cout << index; // 5
}
时间复杂度分析
令 n 为主串长度,m 为要匹配的子串长度。
最理想的情况出现在一开始就匹配成功,不需要回溯,While循环只执行m次,时间复杂度是O(m)。
最坏的情况下时间复杂度为o(n * m), 举个例子。主串为 S="00000000001",匹配串为 T="0001"。在匹配过程中,每次都匹配在T最后一个字符发现匹配失败再次回溯,直到匹配到 S 串的末尾才匹配成功。所以时间复杂度是o(n * m)。
但也不是每次都出现这么极端的情况,平均来说,BF算法的时间复杂度为o(n+m)。
总结
学会BF算法就可以完成Leetcode上的第28题了,BF的效率实在不高,但是BF的思想清晰易懂,下节将讲解BF的改进KMP算法。