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

发布了20 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43382350/article/details/102168930