KMP之个人理解

本文深入浅出地介绍了KMP算法的原理与实现过程,通过对比传统字符串匹配算法,阐述了KMP算法的优势所在,并提供了Java代码示例。

半年前接触到了这个小算法,当时想了足有一整天,看了很多高手的博客,结果一直没理解,直到个人的任督二脉被打通,才发现原来这么简单。
过了半年,突然又想起这个算法,忘了个七七八八,又迷茫了一会,还是写出来免得再忘掉。

KMP算法用处是从一条源字符串中寻找与目标字符串匹配的位置。我这里设源字符串为src,目标字符串为dst。

一般的算法一般是对src每一个位置i对dst头部进行匹配,之后分别用src[i + n]与 dst[n]进行匹配,如果完全匹配就可以获得位置,其时间复杂度是src.length * dst.length;

而KMP算法则要效果好上不少,下面就按我的思路讲解一下
src 0 1 2 3 4 ……. n-2 n-1 n
dst 0 1 2 3 4 …… m-2 m-1 m

假设 dst[ 0 ] ~ dst[ t ] 当前与src[ s ] ~ src[ s + t ]相匹配,dst[ t + 1 ]与src[ s + t + 1 ] 不匹配时,这时候按照一般的方法是检索src[ s + 1 ],并从dst[ 0 ]重新开始。
再做一个假设,假设dst[ 0 ] ~ dst [ x ] (x < t) 与 dst [ t - x ] ~ dst[ t ]逐一匹配(原谅我制作的丑)
dst 0 1 2 3 4 …… x-2 x-1 x

dst t-x t-x+1 t-x+2 t-x+3 t-x+4 …… t-2 t-1 t

src s+t-x s+t - x+1 t-x+2 t-x+3 t-x+4 …… t-2 t-1 t

看到这个,大家想起什么。我们在dst[ t + 1 ]与src[ s + t + 1 ] 不匹配时,完全可以直接跳过中间的步骤,继续检索dst[ x + 1]与
src[ s + t + 1 ] 是否匹配。(因为我们完全可以证明dst[ 0 ] ~ dst [ x ]与src[ s + t - x ] ~ src[ s + t ]相匹配)

我们假设dst[ 0 ] ~ dst [ x ] (x < t) 与 dst [ t - x ] ~ dst[ t ]是最长的匹配字符,即不存在dst[ 0 ] ~ dst [ x + 1 ] (x < t) 与 dst [ t - x - 1 ] ~ dst[ t ]匹配,则当前匹配为最优匹配。我们假设当前匹配皆为最优匹配,至于获得最优匹配的方法后面再讲。

那么我们证明这样可以获得不会错失一些可能存在的解。是否有可能存在一个解y其开始位置位于0~x之间且被我们错过呢。这是不可能的,假如存在,
即dst[ 0 ] ~ dst [ t - y ] 与 src[s + y] ~ src[s + t ]相匹配。则我们同样可以证明dst[ 0 ]~dst[t - y] 与 dst[ y ] ~ dst[ t ]是相匹配的,
由于t - y > t - x (y < x);我们可以发现这与前面的最优匹配不符。因此可以证明最优匹配的匹配开始位置之前不可能存在新的匹配开始位置。

继续下一步,我们如何才能获取最优的跳转位置呢。
我们需要维护一个与dst等长的int型数组jumpPosTable;
jumpPosTable中保存当此次匹配失败,dstPos将要跳转的位置。

遍历dst数组,维护两个位置,分别命名为scanPos和jumpPos,scanPos为扫描位置,jumpPos位置为跳转位置。
具体细节见下面的代码(初学java,练练手)

//KMP类
final class KMP
{
    //获取字符串src与dst的匹配度表
    public static int[] getMateString(String dst, String src)
    {
        //获取dst的跳转表
        int dstLen = dst.length();
        int srcLen = src.length();
        int []jumpTable = KMP.getJumpTable(dst);
        int []mateTable = new int[src.length()];
        char []dstChTable = dst.toCharArray();
        char []srcChTable = src.toCharArray();

        //进行匹配操作
        for(int dstPos = 0, srcPos = 0; srcPos < srcLen; )
        {
            //如果dstPos小于0
            if(dstPos < 0)
            {
                dstPos++;
                srcPos++;
            }
            //如果dstPos溢出
            else if(dstPos >= dstLen)
            {
                dstPos = jumpTable[dstLen - 1] + 1;
                if(dstChTable[jumpTable[dstLen - 1]] != dstChTable[dstLen - 1])
                    dstPos--;
            }
            //如果srcPos位置与dstPos位置字符相同
            else if(dstChTable[dstPos] == srcChTable[srcPos])
                mateTable[srcPos++] = ++dstPos; 
            //如果不同
            else
                dstPos = jumpTable[dstPos];
        }

        return mateTable;
    }

    //获取字符串str的跳转表
    private static int[] getJumpTable(String str)
    {
        char []chTable = str.toCharArray();
        int chLen = str.length();
        int []jumpTable = new int[chLen]; //跳转表

        //生成跳转表
        jumpTable[0] = -1;
        for(int scanPos = 1, jumpPos = 0; scanPos < chLen; scanPos++, jumpPos++)
        {
            jumpTable[scanPos] = jumpPos;

            //如果扫描位置和跳转位置的字符不同
            if(chTable[scanPos] != chTable[jumpPos])
                jumpPos = -1;
        }

        return jumpTable;
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值