• 从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] != '' && SonStr[j]!='')   //两个都没到尾部
    	{
    		if (MotherStr[i] == SonStr[j])   //如果相等两个指针都递增
    		{
    			i++;
    			j++;
    		}
    		else
    		{
    			i = i - j + 1;   //回溯
    			j = 0;
    		}
    	}
    	if (SonStr[j] == '')
    	{
    		//如果子串指针指向了'',表示匹配完成
    		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
    ", 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:" << '
    ';
        for(int i = 0; i < n; i++){
            //看一下prefixtable是否正确
            cout << prefix[i] << '
    ';
    
        }
        char text[] = "abababcabaabababab";
        kmp_search(text, pattern);
    }
    
    
  • 相关阅读:
    使用Git进行代码管理的心得
    软件工程的实践项目的自我目标
    第五次作业——团队项目——需求规格说明书
    第三次作业——结对编程
    第六次作业——团队作业
    调研Android平台的开发环境的发展演变
    第四次作业——个人作业——软件案例分析
    IT人士 不能一辈子靠技术生存
    Launchy、FARR、AutoHotKey哪个快速启动软件好?
    申请了好几次,终于通过了!庆祝一下!
  • 原文地址:https://www.cnblogs.com/ygbrsf/p/12519601.html
Copyright © 2020-2023  润新知