对于字符串而言,最常见的基本操作莫过于查找某一字符串(模式串)在另一字符串(主串)中的位置,这一操作过程叫做字符串的模式匹配,常见的模式匹配算法有朴素模式匹配算法和KMP模式匹配算法,下面结合代码对这两种模式匹配算法的思想做个总结。
参考博客:很详尽的KMP算法(厉害)
1.朴素模式匹配算法(暴力法)
朴素模式匹配算法的思想就是,把主串中的每一个字符作为子串开头,与要匹配的字符串进行逐字符匹配,直到所有字符匹配成功或全部遍历完成为止。
#include <iostream>
using namespace std;
/* 返回模式串P在主串S中的位置,若不存在,则函数返回值为-1 */
int index(const char* s, const char* p)
{
int sLen = strlen(s);
int pLen = strlen(p);
int i = 0; // i用于主串s中当前位置的下标
int j = 0; // j用于模式串p中当前位置的下标
while (i < sLen && j < pLen)
{
if (s[i] == p[j]) // 两字符相等,则递增i和j,比较下一位置字符
{
i++;
j++;
}
else // 两字符不等
{
i = i - j + 1; // 回退i,回到上次匹配开始的首位的下一位
/* 此时T[j]与S[i]匹配失败,而j是从0开始递增的,表明前j个字符匹配都是成功的,
由此可得到i是从(i-j)开始递增的,则下次匹配从(i-j+1)开始。
*/
j = 0; // 将j重置为0
}
}
if (j == pLen)
{
return i - j;
}
else
{
return -1;
}
}
int main() {
const char* S = "ABCDABD";
const char* P = "ABD";
int ret = index(S, P);
cout << "ret = " << ret << endl;
}
2.KMP模式匹配算法
KMP算法相比朴素模式匹配算法效率提升了很多,因为它避免很多不必要的比较操作,KMP算法过程只更新j,而不回退i,前提是先计算出表示模式串下标j的变化数组next,next 数组各值的含义:代表当前字符之前的字符串(下标为0,1,…,j-1)中,有多大长度的相同前缀后缀,例如如果next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀。
#include <iostream>
#include <string>
using namespace std;
/* 计算KMP算法中模式串P下标j的变化数组next */
void get_next(string P, int* next) {
int pLen = P.size();
next[0] = -1;
int k = -1; // 前缀末尾字符下标
int j = 0; // 后缀末尾字符下标
while (j < pLen - 1)
{
// p[k]表示前缀,p[j]表示后缀
if (k == -1 || P[k] == P[j])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
/* KMP算法:返回模式串P在主串S中的位置,若不存在,则函数返回值为-1 */
int index_KMP(string S, string P, int* next) {
int sLen = S.size();
int pLen = P.size();
int i = 0;
int j = 0;
while (i < sLen && j < pLen)
{
/*
备注:
这里的 j == -1,是考虑到next[0]值为-1的情况,若j为-1,就不能作为P[j]的索引了
*/
if (j == -1 || S[i] == P[j]) // 当前字符匹配成功,继续匹配下一字符
{
i++;
j++;
}
else // 当前字符匹配失败,更新j为next[j],i不变
{
j = next[j];
}
}
if (j == P.size())
{
return i - j;
}
else
{
return -1;
}
}
int main() {
string S, P;
cout << "Please input string S: ";
cin >> S;
cout << "Please input string P: ";
cin >> P;
int* next = new int[P.size()];
get_next(P, next);
// print next array
cout << "next: " << endl;
for (int i = 0; i < P.size(); i++)
{
cout << next[i] << "\t";
}
cout << endl << endl;
int ret = index_KMP(S, P, next);
cout << "ret = " << ret << endl;
delete[] next;
}
运行结果: