从BF算法到kmp算法详解
正文索引
一、KMP介绍
二、例子:子串匹配母串
1.BF算法的解决方法
三、kmp算法的实现
(1)为什么已经有BF算法了还要有KMP算法呢?
(2)发明的算法基本思想
(3)具体实现
一、KMP介绍
KMP算法是一种改进的字符串匹配算法(有BF算法改进而来,BF算法是暴利搜索匹配的方式,而KMP则是对BF算法的回溯过程进行改进,从而大幅度降低了时间复杂度),能够很好地处理子串与母串的匹配
二、例子:子串匹配母串
母串:a b a a c a b a b c a c
子串:a b a b c
要求子串与母串进行匹配,求解在哪一个位置匹配上了。
1.BF算法的解决方法
关键词:逐一匹配 暴力搜索
第一步匹配
母串:a b a a c a b a b c a c
子串:a b a b c
匹配结果:第四个位置匹配失败
第二步匹配
母串:a b a a c a b a b c a c
子串: a b a b c
匹配结果:第一个匹配位置失败
第三步匹配
母串:a b a a c a b a b c a c
子串: a b a b c
匹配结果:第二个位置匹配失败
第四个位置
母串:a b a a c a b a b c a c
子串: a b a b c
匹配结果:第二个位置匹配失败
第五个位置
母串:a b a a c a b a b c a c
子串: a b a b c
匹配结果:第一个位置匹配失败
第六个位置
母串:a b a a c a b a b c a c
子串: a b a b c
匹配结果:匹配成功,返回位置6
以上就是BF算法的匹配过程,逐一移动,每个位置都尝试一遍
i 回溯到开始位置+1
j 回溯到子串的0位置
推理过程:j的长度实际上等于i向左移动的位置,那么要返回开始的位置再加1就可以表示成 i – j + 1
int BFstring(string MotherStr, string SonStr){
int i = 0, j = 0;
for(;(i != MotherStr.size()) && (j != SonStr.size());){
if(MotherStr[i] == SonStr[j]){
i++, j++;
}
else{
i = i - j + 1;
j = 0;
}
if(j == SonStr.size()){
return i - j + 1;
}
}
return 0;
}
int BFchar(char MotherStr[],char SonStr[]){
int i, j;
i = 0;//主串指针
j = 0;//子串指针
while (MotherStr[i] != \'\0\' && SonStr[j]!=\'\0\') //两个都没到尾部
{
if (MotherStr[i] == SonStr[j]) //如果相等两个指针都递增
{
i++;
j++;
}
else
{
i = i - j + 1; //回溯
j = 0;
}
}
if (SonStr[j] == \'\0\')
{
//如果子串指针指向了\'\0\',表示匹配完成
return i - strlen(SonStr) + 1;
}
return -1;
}
三、kmp算法的实现
(1)为什么已经有BF算法了还要有KMP算法呢?
可以看一下下面这个例子
a a a a a a a a a a a a a a a a a a a b
a a a a b
如果是使用BF匹配的话,每次都是在最后一个位置才发现本趟匹配失败,于是每次匹配都是最大的时间复杂度,这也就是BF算法的最坏情况。
算法发明者:knuth-morris-pratt
(2)发明的算法基本思想
是当出现不匹配时,我们已经能知晓一部分文本的内容(因为在匹配失败之前它们已经和模式相匹配)。我们可以利用这些信息避免将指针回退到所有这些已知的字符之前。
(3)具体实现
用的还是这个例子
母串:a b a a c a b a b c a c
子串:a b a b c
prefix table
找出最长前缀和最长后缀,并且最长前后缀相同,那么我们可以计算出下面子串的最长公共前后缀(不能是子串本身哦)
a -1(第一个最长公共前后缀是定义的特殊值-1和字符串本身无太大关系)
a b 0
a b a 1
a b a b 2
a b a b c 0
得到最长公共前后缀表
子串:a b a b c
-1 0 1 2 0
这回我们BF中的i和j,i返回的值就不需要是i – j + 1而可以直接返回next数组值从而减少回溯的距离
(回溯距离越短,时间降低的越多)
#include <bits/stdc++.h>
#define REP(i, a, b) for(int i = a; i < b; i++)
#define REP_(i, a, b) for(int i = a; i <= b; i++)
#define sl(n) scanf("%lld", &n);
#define si(n) scanf("%d", &n);
#define RepAll(a) for(auto x: a)
#define cout(ans) cout << ans << endl;
typedef long long ll;
void prefix_table(char pattern[],int prefix[],int n){
prefix[0] = 0;
int len = 0;
int i = 1;
while(i < n){
if(pattern[i] == pattern[len] ) {
len++;
prefix[i] = len;
i++;
}
else {
if(len > 0)
len = prefix[len - 1];
else
prefix[i] = len, i++;
}
}
}
void move_prefix_table(int prefix[], int n){
for(int i = n-1; i > 0; i--){
prefix[i] = prefix[i - 1];
}
prefix[0] = -1;
}
void kmp_search(char MotherStr[], char SonStr[]){
int n = strlen(SonStr);
int m = strlen(MotherStr);
int *prefix = new int [n];
prefix_table(SonStr, prefix, n);
move_prefix_table(prefix, n);
//MotherStr[i] len(MotherStr) = m;
//SonStr[j] len(SonStr0 = n;
int i = 0, j = 0;
while(i < m){
if (j == n - 1&& MotherStr[i] == SonStr[j]){
printf("Found pattern %d\n", i - j);
j = prefix[j];
}
if (MotherStr[i] == SonStr[j]){
i++, j++;
}
else {
j = prefix[j];//回溯
if(j == -1){
//特殊点
i++, j++;
}
}
}
}
int main(){
char pattern[] = "ababcabaa";
int prefix[9];
int n = 9;
prefix_table(pattern, prefix, n);
move_prefix_table(prefix, n);
cout << "prefix table:" << \'\n\';
for(int i = 0; i < n; i++){
//看一下prefixtable是否正确
cout << prefix[i] << \'\n\';
}
char text[] = "abababcabaabababab";
kmp_search(text, pattern);
}