zoukankan      html  css  js  c++  java
  • KMP算法番外篇--求解next数组

    KMP算法实现字符串的模式匹配的时间复杂度比朴素的模式匹配好很多,但是它时间效率的提高是有前提的,那就是:模式串的重复率很高,不然它的效率也不会凸显出来。在实际的应用中,KMP算法不算是使用率很高的一个算法,但是它的核心的那点东西却是使用率很高的,那就是next前缀数组的求解思路。在这次笔记中就单独摘出来,说一下前缀数组的求解。


    1. next前缀数组的定义

    image

    不管做题还是推到算法,永远记住定义,这时最重要的东西。


    2. next数组的暴力求解

    这种方法的主要思路是:

    为了求解nj的值,把clip_image002的所有的前缀和后缀都找出来,然后从最大的开始匹配,直到找到合适的最长公共前缀后缀。如果没有,那么nj的值就是0。

    前后缀的选取方式:

    暴力算法就是在这里面不断的从最大的那个前缀和后缀逐一的匹配。

    算法描述:

    (1) 根据定义,初始化n[0] = –1。

    (2) 从模式串的下标为1的位置,依次遍历整个模式串。对于每一个字符,当到达其下标j时,令k=j-1。

    (3) 寻找它前面的字符串的最大公共前缀后缀,也就是判断clip_image002[3]的真假?

    (4) 如果满足条件,令next[j]=k;如果不满足条件k--,继续执行(3)的步骤,直到k==0,然后令next[j]= 0。

    代码实现:

    #include <iostream>
    #include <stdlib.h>
    bool IsPatternMatch(char *p, int compareNum, int totalNum);
    void ViolentGetNext(char *p, int *next);
    
    void main()
    {
        int next[20];
        char *str = "agctagcagctagctg";
        ViolentGetNext(str, next);
    
        system("pause");
    }
    
    void ViolentGetNext(char *p, int *next)
    {
        int pLen = strlen(p);
        int k = 0;
        next[0] = -1;
    
        for(int j = 1; j < pLen; j++)
        {
            k = j - 1;
            while(k > 0)
            {
                if(IsPatternMatch(p, k , j))
                    break;
                else
                    k--;
            }// while
    
            next[j] = k;
        }// for
    }
    //param:copareNum代表了要比较的字节数
    //param:totalNum代表了要比较的字节数
    //上面的两个参数的作用就是定界前缀和后缀可能的范围
    bool IsPatternMatch(char *p, int compareNum, int totalNum)
    {
        int i = 0;
        int j = totalNum - compareNum;
    
        for(; i < compareNum; i++, j++)
        {
            if(p[i] != p[j])
            {
                return false;
            }
        }
    
        return true;
    }
    具体的例子,假设字符串为ABCDABD
    n[5]的求解过程如下:
    k=4 
    ABCD≠BCDA,k=3 
    ABC≠CDA,k=2 
    AB≠DA,k=1 
    A==A,n[5]=k

    3. next数组的递归求解

    暴力求解每次在计算next[j]的时候都是独立的,而实际上求解next[j+1]是可以利用到next[0…j]的,这里的递归算法就是这样实现的。

    设模式串为clip_image002[7],现在已经计算出了next[0…j],如何计算next[j+1]?

    利用前面求解的数值(这也是算法改进的地方,不让每个next元素都独立的计算),若已知next[j]=k,则对于模式串,肯定有这样的关系:

    clip_image002[3]

    所以算法的描述可以是这样的:

    (1) 如果k==-1(只有第一个字符的next值是-1),说明现在的位置是第二个位置,还不能算第二个它本身,所以next[j+1]=0,算法结束。

    (2) 如果clip_image002[10],理解这里的k是怎么从next[j]的值转换到了字符的下标值。则next[j+1]=k+1,算法结束。

    提示:前面有分析过,求解next数组的过程的快捷方法就是不让他们独立的计算,还是继承前面计算好了的对称性。知道了next[j]的对称性,只需要在考察一下前缀和后缀的下一个字符是否相等就可以了。pk和pj就是之前最长前缀和后缀的下一个字符。

    (3) image那么这个k’从哪里来的呢?看这个式子的两端就知道k’=next[k]。理解好上面的这个式子,就知道k‘是怎么来的了。

    (4) 将k’赋值给k,转到步骤(1)。

    代码实现:

    //the recursion method to abtain the next array
    //pLen is the length of the string
    void RecursionGetNext(char *p, int pLen, int *next)
    {
    
        if(pLen == 1)
        {
            next[pLen - 1] = -1;
            return;
        }
    
        RecursionGetNext(p, pLen - 1, next);
    
        //pLen represents the number of the string
        //pLen - 1 represents the index of the last character,that is the character that will be calculated in the next array.
        //pLen - 1 - 1 represents the index of the sub-last character that has been calculated in the next array.
        int k = next[pLen - 2];
    
        //k==-1 is a label showing that there is no prefix matching with postfix and the currently added character can not match neither.
        //k==0 can only show that there is no prefix mathching with postfix,but pk may be match with pj
        while(k >= 0)
        {
            if(p[pLen-2] == p[k])
            {
                break;
            }
            else
            {
                k = next[k];
            }
        }//while
    
        next[pLen -1] = k + 1;
    
    }//RecursionGetNext()

    4. next数组的递归展开求解

    void GetNext(char *p, int *next)
    {
        int pLen = strlen(p);
        int j = 0;
        int k = -1;
        next[0] = -1;
    
        while(j < pLen - 1)
        {
            //accroding to the depiction of the algorithm,the procedure can be programmed below:
            //if(k == -1)
            //{
            //    ++j;
            //    ++k;
            //    next[j] = k;
            //}
            //else if(p[j] == p[k])
            //{
            //    ++j;
            //    ++k;
            //    next[j] = k;
            //}
            //but the fist two procedure can be reduced to one case:
    
            //p[j] == p[k] shows that we can inherite the feature of the string that matched alreay
            //k==-1 shows two circumstance: 1.the beginning of the algorithm 2.there is no matched prefix and postfix and the last character is also defferent with the first one
            if(k == -1 || p[j] == p[k])
            {
                ++j;
                ++k;
                next[j] = k;
            }
            else
            {
                k = next[k];
            }
        }//while
    }

    5. Reference Link

    @wzhang1117的博客:https://www.zybuluo.com/wzhang1117/note/27431

  • 相关阅读:
    android语音识别 android.speech 包分析
    [Android]MIT App Inventor
    How C/C++ Debugging Works on Android
    Android JNI相关
    Google非官方的Text To Speech和Speech Recognition的API
    Cygwin/MinGW
    VoxForge collect transcribed speech for use with Free and Open Source Speech Recognition Engines
    Voice Search/Actions for Android
    如何查看网页编码
    [转]Python__builtin__与__builtins__的区别与关系(超详细,经典)
  • 原文地址:https://www.cnblogs.com/stemon/p/4354221.html
Copyright © 2011-2022 走看看