從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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章