KMP算法详解 C++

KMP详解


KMP算法是基于BF算法山改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出。在KMP之前需要先掌握BF算法。


BF算法

BF算法,即暴力匹配,每次匹配失败父串都要回溯到 i1+1 位置,i2回溯到头部,在进行下一次匹配。

在这里插入图片描述

最坏情况下,需要匹配 ( N * M )次。

在这里插入图片描述

代码示例:

int strStr(string haystack, string needle) {
        if(needle.size() == 0)
            return 0;
        if(haystack.size() == 0 || needle.size() > haystack.size())
            return -1;

        int i1 = 0;
        int i2 = 0;

        while(i1 < haystack.size())
        {
            if(haystack[i1] != needle[i2])
            {
                i1++;  //不匹配,继续向后寻找首匹配字符
            }
            else
            {
                int ii1 = i1; 
                int ii2 = i2;
                while(ii1 < haystack.size() && ii2 < needle.size() && haystack[ii1] == needle[ii2])
                {
                    ii1++;
                    ii2++;
                }

                if(ii2 == needle.size()) //当ii2 走到空,说明ii2为子串
                    return i1;

                i1++;
            }

            
        }

        return -1;
    }


KMP


首先,需要先给出一个概念 前后缀相等的最大长度 这里简化称为 maxfix 例如 aaaamaxfix 就是aaa。

举例:
在这里插入图片描述

在这里插入图片描述

有了这个概念,我们要得知的是一个字符串,从每个位置开始,前面的前后缀相等的最大长度,不包括当前字符。

我们需要一个数组保存所有下标的maxfix ,这个数组就叫next数组,它保存着所有位置开始前面的前后缀相等的最大长度 next数组下标为 0 位置和下标为 1 位置都是固定的 -1 和 0, 它们的左边都不可能存在前后缀相等的最大长度。 这个next数组是针对子串,保存的是子串的信息。我们先假设已求出next数组,利用已求出的next数组进行KMP。
在这里插入图片描述


KMP过程

假设,此时正在匹配,i1 和 i2 向后走,当父串[i1] = x 子串[i2] = y,不相等,如果是BF算法,此时i1 回溯到i1 + 1 位置,i2回溯到开头,继续匹配。
在这里插入图片描述

KMP过程是这么做的。

maxfix可能存在交错情况,这里只举例无交错情况。(图中蓝框)

在这里插入图片描述
为什么可以这样跳过,图示x 前面的字符串和 y前面的字符串相等,最长后缀肯定对应相等,而最长前缀和最长后缀相等,必然相等。

在这里插入图片描述

还有一点,能这样跳跃比较的的前提 必须证明 i1 到 k 不可能求得 子串 在 父串 里。

在这里插入图片描述


证明过程:

假定 j 位置开始,可以匹配出子串 为 父串一部分。
在这里插入图片描述
那么一定有:

前提已经说过了,i1 和 i2 已经走到了不等处,前面的字符串必然相等,显而易见,sub1 等于 sub2 。

那么,如果k之前存在一个下标开始的字符串,匹配上了子串,那这至少说明,子串开始向后走 j 到 x 的距离,也就是sub3 ,这个sub3等于sub1 。sub3 又等于 sub2 。但是, 这可能吗?前面已经假定已经算出了next数组,我们知道子串上每个字符位置前的最大前后缀相等的最大长度。如果k 之前存在成功匹配位置。潜台词就是有一个更大的前后缀相等的最大长度 。(sub2、sub3为更大的maxfix)。只要能保证求出的next数组是正确的,就不可能出现,证明完成。

在这里插入图片描述


示例:

i2 回溯到 d 位置 ,相当于从a开始,往后匹配,是否包含下面这个子串。

在这里插入图片描述


举一个完整的例子

y下标保存的 前后缀相等的最大长度 为7,往前回溯只要让 i2 = next[i2]
当i2回溯到0时,匹配串无法向后推了,i1向后移动。
在这里插入图片描述


KMP过程代码示例:

 int KMP(string haystack, string needle) {    
        if(needle.size() == 0)
            return 0;
        if(haystack.size() == 0 || haystack.size() < needle.size())
            return -1;

        //建立next数组    
        int* next = getNextArray(needle);

        int i1 = 0,i2 = 0;
        while(i1 < haystack.size() && i2 < needle.size())
        {
            //相等,i1 i2 一直走
            if(haystack[i1] == needle[i2])
            {
                i1++;
                i2++;
            }
            else if(next[i2] != -1) // 还可以写成 i2 != 0    还有前缀的情况下,i2向前跳
            {
                i2 = next[i2];
            }
            else{ //没有前缀,needle已经无法向右推了,只能让i1向后走
                i1++;
            }
        }

        delete[] next;
        //i2 == ne.size() 说明n是s的子串,i1- 子串的长度 就是开头
        return i2 == needle.size() ? i1 - i2 : -1;
    }

next数组

next数组中下标为0 和 1位置的值是固定的,下标 2 位置的maxfix可以通过0,1位置求得,下标3位置的maxfix可以通过下标 0,1,2求得。下标4 位置的maxfix可以通过下标 0,1,2,3求得。。。。。

在这里插入图片描述

那么下标为 i 位置的最大前后缀长度为

通过判断 i-1 位置字符是否和

在这里插入图片描述

不匹配情况

在这里插入图片描述


next数组代码示例:

    int* getNextArray(string& needle)
    {
        if(needle.size() == 1)
        {
            int* next = new int[1];
            next[0] = -1;
            return next;
        }

        //0位置-1 1位置0
        int* next = new int[needle.size()];
        next[0] = -1;
        next[1] = 0;
        
        int prev = 0; //prev下标的字符和 i-1 下标字符比较,  prev既代表比较的下标还代表当前的maxfix长度。
        int i = 2; //初始i= 2  0 和 1 位置比较是否相等。

        while(i < needle.size())
        {
            if(needle[i - 1] == needle[prev])
            {
                next[i++] = ++prev;
            }
            else if(next[prev] != -1)
            {
                prev= next[prev];
            }
            else 
            {
                next[i++] = 0;
            }

        }

        return next;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

necesse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值