模式匹配
BF算法
暴力解
1.两串的起始下标为0
2.循环结束的条件:子串遇到\0→成功返回位置,主串遇到\0(长度减子串长度的位置) →失败
3.比较:s = t,下一位; s != t 回溯
4.回溯:s的指针i回到主串i-j+1 位置 j回到子串头部
时间复杂度
S长度:n T长度:m
最好情况:不成功的匹配都发生在T的第一个字符
不成功趟数的比较次数----1*(i-1)次
成功趟数的比较次数----m次
所有匹配成功的可能情况有n-m+1
(n+m)/2
O(n+m)
最差情况:不成功的匹配都发生在T的最后一个字符
不成功趟数的比较次数----m*(i-1)次
成功趟数的比较次数----m次
一共比较了i*n次
m(n-m+2)/2
O(n*m)
KMP算法
KMP改进了BF的回溯算法,利用了已经部分匹配的结果,主串中的i不回溯,只是子串滑动,省去了没必要的回溯,大大提高了效率。
分清:前缀后缀 前缀后缀相当于一个“标识”
T移动位数 = 子串已匹配的字符数 - 失配位置前的最长前缀匹配字符数
j对齐位置:失配位置前的最长前缀匹配字符数+1 - 1(+1是前缀的后一位 -1是数组从0存储)
next数组的三种取值
- 当j = 0时, next[j]=-1 不进行字符比较 此时要做i++ j++ (同前面比较:s[] = t[]的情况)
- 当j > 0 时, next[j]的值为:前缀后缀相同的长度
- 当前缀后缀相同的长度为0时,next[j]=0 回到头部开始比较
Next数组
next数组的获取极难理解
建议边看void getNext(char* T, int* n)边完成以下表格:
ij位置 | j起始位置 | i起始位置 | ||||||
---|---|---|---|---|---|---|---|---|
ij对应的值 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
T串 | 无 | a | b | c | d | a | b | d |
next数组 | 无 | -1起始值 |
void getNext(char* T, int* n)//只对子串T进行处理
{
int lenT = strlen(T);
int j = -1;//j是前缀
int i = 0;//i是后缀
n[0] = -1;//next数组
while (i < lenT)//i在T内
{
if (j == -1 || T[i] == T[j])
{
i++; j++;
n[i] = j;//这里的i j是已经加一的值
}
else
{
j = n[j];
}
}
}
运行完上面代码表格如下:
ij位置 | j最终位置 | i最终位置 | ||||||
---|---|---|---|---|---|---|---|---|
ij对应的值 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
T串 | 无 | a | b | c | d | a | b | d |
next数组 | 无 | -1起始值 | 0 | 0 | 0 | 0 | 1 | 1 |
源代码
#include <iostream>
#include <cstring>
using namespace std;
int BF(char S[], char T[])
{
int i = 0, j = 0;
while (S[i] != '\0'&& T[j] != '\0')//可以直接写0 S[i] && T[j]
{
if (S[i] == T[j])
{
i++; j++;
}
else
{
i = i - j + 1; j = 0;
}
}
if (T[j] == '\0')//成功
return i - j;//从1开始就是 i-j+1
if (S[i] == '\0')//失败
return 0;
}
void getNext(char* T, int* n)//只对子串进行处理
{
int lenT = strlen(T);
int j = -1;//j是前缀
int i = 0;//i是后缀
n[0] = -1;
while (i < lenT)
{
if (j == -1 || T[i] == T[j])
{
i++; j++;
n[i] = j;//这里的i j是已经加一的值
}
else
{
j = n[j];
}
}
}
int KMP(char S[], char T[])
{
int i = 0, j = 0;
int lenS = strlen(S);
int lenT = strlen(T);
int next[100];//不能用lenT去初始化数组
getNext(T, next);//传入地址,数组名就是地址
//while(S[i] != '\0'&& T[j] != '\0')//不能用 '\0',因为j=-1数组越界
while (i < lenS - lenT + 1 && j < lenT)
{//可以计算出字符串长度 strlen
if (j == -1 || S[i] == T[j])
{
i++; j++;
}
else
{
j = next[j];
}
}
if (j == lenT)//成功
return i - j;//若数组从1开始就是 i-j+1
if (i == lenS - lenT + 1)//失败
return 0;
}
int main()
{
char S[] = "ahksafafhjehfjkhfkjahkjhrjkfhksbvbfsjvb";
char T1[] = "jkhfkja";
char T2[] = "axkahdh";
if (KMP(S, T1)) { cout << "成功,位置是:" << BF(S, T1) << endl; }
else { cout << "没有找到匹配值"; }
if (KMP(S, T2)) { cout << "成功,位置是:" << BF(S, T2) << endl; }
else { cout << "没有找到匹配值" << endl; }
system("pause");
return 0;
}
结果
严格来说,若数组从1开始应该是14位,改下输出就好