正文索引
一、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);
}