KMP优化

KMP优化

KMP算法是有优化版本的,之前写过文章简单的讨论了基本的KMP算法思想,但是对于一些含有连续重复字符的字符串,会出现多次冗余的比较。
例如:字符串S=“aaababa”和模式串P=“aaac”比较时,会出现多次的不必要的比较,这个我会在下面细说。

Next数组重定义

我们首先要解决的问题就是next数组的意义,之前的我写的KMP文章里面,next数组的意义是:字符串真子串中既是前缀串同时又是后缀串中最长的那个子串的长度。例如还是模式串P=“aaac”,其对应的next数组值为:

子串aaaaaaaaac
next[]0120

为了优化KMP,我们要换一种写KMP代码的思路,参考网上的代码及next数组的定义,我们首先就是需要重新改变一下next数组,我们现在定义next[i]为:表示匹配串在i处如果匹配失败下次移到的位置。 如果第一个字符就匹配不成功,那么没有地方移动,我们就定义next[0] =-1.

于是模式串P=“aaac”对应新的next数组为:(不考虑优化)

子串aaaaaaaaac
next[]-1012

相当于把之前的next数组整体向右移了1位,并在开始处加入-1,重新定了next数组之后,我们的KMP算法该怎么写呢?如下:

//求next数组
void getNext()
{
    int i = 0; //pattern串的下标
    int j = -1; //
    next[0] = -1;
    while (i < pattern_len - 1)
    {
        if (j == -1 || pattern[i] == pattern[j])
        {
            ++i;
            ++j;//i,j相加之后pattern[0..j-1]和pattern[i-j....i-1]是相等的
            next[i] = j;//pattern[i]位字符匹配不成功时应该重新回到pattern[j]位进行匹配
        }
        else
            j = next[j];
    }
}
int kmp()
{//字符串比较过程
    int i = 0, j = 0;
    str_len = strlen(str);
    getNext(); // 计算next数组;
    while (i < str_len && j < pattern_len)
    {
        if (j == -1 || str[i] == pattern[j])
        {
            ++i;
            ++j;
        }
        else
            j = next[j];
    }
    if (j >= pattern_len)
        return i - pattern_len;
    else
        return -1;
}

Next数组优化

重新定义了next数组实现代码之后,我们发现这和之前的KMP算法(算法导论上的实现方式)实现效果本质是一样的,出现不匹配时充分利用了之前的已匹配的信息,然后将字符串进行转移,但是还是会发生一开始提到到的字符串S=“aaababa”和模式串P=“aaac”多次冗余比较的问题。我们仔细来看一下,现在的比较过程:

《KMP优化》

  • 当pattern[3] = ‘c’ 和 s[3] = ‘b’发生了不匹配的时候,我们需要移动pattern的比较index,这时候移动到了next[3] = 2
  • pattern[2] = ‘a’ 和 s[3] = ‘b’仍然不匹配,继续移动,移动到next[2]=1
  • pattern[1] = ‘a’ 和 s[3] = ‘b’仍然不匹配,继续移动….移动到next[1]=0
  • …………………..好累

出现这样的原因是因为pattern的前三个字符都是a,第四个字符c和前面的字符都不相同,所以当第四个字符不匹配的时候,前面都没有必要再比较了 , 所以说我们在求next数组的时候还有没利用的信息。当不匹配发生移动的时候,我们可以判断当前pattern[i]字符与pattern[j](也就是假想的要移动到的index)是否相同

  • 若不相同,和之前是一样的next[i]=j(下一次匹配pattern[j])
  • 若相同,那么就直接next[i] = next[j] (在求next数组的过程中避免了多次冗余的比较)

实现代码如下(参考网上):

//优化算法,求next数组的值
void getNext2()
{
    int i = 0; //pattern串的下标
    int j = -1; //
    next[0] = -1;
    while (i < pattern_len - 1)
    {

        if (j == -1 || pattern[i] == pattern[j])
        {
            ++i;
            ++j;
            if (pattern[i] != pattern[j]) //正常情况
                next[i] = j;
            else //特殊情况,这里即为优化之处。考虑下AAAB, 防止4个A形成012在匹配时多次迭代。相当于next[3]=next[2]=next[1]=next[0]=-1
                next[i] = next[j];
        }
        else
            j = next[j];
    }
}

此时匹配的过程如下:
《KMP优化》

此时不需要多次冗余的比较….而最终的KMP比较算法是不变的,这里就不再重复给出。

总结

本文给出了优化版的KMP算法,之前的算法导论版本的KMP代码的优化实现暂时还没想出…. 而本文优化的关键之处是改变了之前的next数组的意义,从之前的真子串中既是前缀串同时又是后缀串中最长的那个子串的长度 转变为求发生不匹配时要移动的新位置,两者是相同的,只是最后的value值不同。
记录一下,以便以后快速回忆起来。

参考链接:

http://www.acmerblog.com/kmp-algorithm-2-4411.html

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/zhaoyunfullmetal/article/details/48105045
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞