定义
字符串简称串,计算机上非数值处理的对象基本都是字符串数据。
子串查找算法(模式匹配算法)
从一个串中查找另一个子串(模式串)的过程
BF算法
比较"暴力"的字符串匹配算法,效率低。
算法复杂度分析
设主串的长度为n,子串的长度为m
时间复杂度 | 空间复杂度 |
---|---|
O(n*m) | O(1) |
代码实现
cpp
#include <iostream>
using namespace std;
int BF(string s, string t) {
int i = 0; // 主串下标
int j = 0; // 子串下标
while (i < s.size() && j < t.size()) {
if (s[i] == t[j]) { // 对应字符相等
i++;
j++;
} else { // 不相等
i = i - j + 1; // 复位i, 指向之前开始匹配字符的下一个字符
j = 0;
}
}
// 子串在主串中找到了
if (j == t.size()) {
return i - j;
}
return -1;
}
int main() {
string s = "ABCDCABDEFG";
string t = "ABD";
// 找到子串,返回子串在主串中的起始下标,如果找不到,返回-1
int pos = BF(s, t);
cout << "pos = " << pos << endl;
return 0;
}
测试
sh
➜ build git:(main) ✗ ./BFString
pos = 5
KMP算法
-
BF算法中对于子串的形状没有做任何的分析,导致匹配过程中做了很多无效的匹配操作,导致算法效率的降低。KMP算法进行了改进,在匹配过程中主串不用回退,提高了算法效率。
-
KMP算法的核心思想就是zifu失配后,主串的i不做回退操作,只回退子串的j。由于在任意一个字符匹配时都有可能失配,所以KMP算法的关键就是给子串计算出一个next数组,里面存储的是当前字符失配时j要回退到的位置,也就是存储的是当前字符前面的子串的公共前后缀的长度。
算法复杂度分析
设主串的长度为n,子串的长度为m
时间复杂度 | 空间复杂度 |
---|---|
O(n + m) | O(m) |
代码
cpp
#include <iostream>
#include <memory>
using namespace std;
// KMP算法求解子串的next数组
int* getNext(string str) {
int* next = new int[str.size()];
int j = 0; // j用来遍历子串
int k = -1; // k 表示公共前后缀的长度
next[j] = k; // 第0个位置设置为-1
while (j < str.size() - 1) {
if (-1 == k || str[k] == str[j]) {
j++;
k++;
next[j] = k;
} else {
// 不相等,做k值的回退
k = next[k];
}
}
return next;
}
int KMP(string s, string t) {
int i = 0; // 主串下标
int j = 0; // 子串下标
// 计算一个子串对应的next数组
int* next = getNext(t);
unique_ptr<int> ptr(next); // delete掉next数组,释放内存
int size1 = s.size();
int size2 = t.size();
while (i < size1 && j < size2) {
if (-1 == j || s[i] == t[j]) { // 对应字符相等
i++;
j++;
} else { // 不相等
// KMP的核心是不回退i值,只回退j值
j = next[j]; // 如果首字母匹配失败,这里j = -1
}
}
// 子串在主串中找到了
if (j == t.size()) {
return i - j;
}
return -1;
}
int main() {
string s = "ABCDCABDEFG";
string t = "ABD";
// 找到子串,返回子串在主串中的起始下标,如果找不到,返回-1
int pos = KMP(s, t);
cout << "pos = " << pos << endl;
return 0;
}
测试
sh
➜ build git:(main) ✗ ./KMPString
pos = 5
KMP算法优化
优化关键是优化next数组
cpp
// KMP算法求解子串的next数组
int* getNext(string str) {
int* next = new int[str.size()];
int j = 0; // j用来遍历子串
int k = -1; // k 表示公共前后缀的长度
next[j] = k; // 第0个位置设置为-1
while (j < str.size() - 1) {
if (-1 == k || str[k] == str[j]) {
j++;
k++;
// KMP算法优化
if (str[k] == str[j]) {
next[j] = next[k]; // 关键,回退的优化
} else {
next[j] = k;
}
} else {
// 不相等,做k值的回退
k = next[k];
}
}
return next;
}