KMP

作者: _假行僧_ | 来源:发表于2020-09-25 10:53 被阅读0次
    KMP有什么用

    KMP主要应用在字符串匹配上。

    KMP的主要思想是「当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。」

    所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。
    next数组也可理解为前缀表,前缀表有什么作用呢?
    「前缀表是用来回溯的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。」

    为了清楚的了解前缀表的来历,我们来看一个例子:


    前缀表.png

    如上图所示,模式串 "ACTGPACY",观察一下"Y"前面的字符串"ACTGPAC",最长相等前缀和后缀为"AC",而Y所对应的2,是匹配失败时,模式字串应该回溯的位置,也即,模式串字符'T'的位置2.具体来看一个示例:


    KMP匹配.png

    如上图,用字符串P="ACTGPACY",匹配字符串S="ACTGPACTGKACTGPACY"。在主串S[i]='T'时匹配失败,此时P匹配到P[j]='Y',对P[j]前的字符串最长相等前缀和后缀"AC",字串指针回退到P[2]='T'的位置,继续和主串匹配。

    求解next数组

    从上图中(前缀表)可以看出,就是计算当前字符前字符串相等的前缀的长度。这里有一个问题需要注意一下,就是next[0]=-1,为何这里会初始化为-1,而不是0?
    当匹配失败时,只有模式字串的指针会回溯,而主串的指针时不变的(第一位匹配失败时除外,此时不前进指针就无法匹配了),因此,如果第一位就失配了,那么如果i不移动,j调到next[j],也就是next[0],假如还是0的话,则就永远不匹配,死循环。因此我们要让i在这种情况下,强行移动一格,将next[0]设为-1,然后判断条件: if(0 > j || S[i] == P[j]),当j==-1时表示第一位匹配失败,此时就需要移动主串指针了。求解next数组具体代码如下:

    int* buildNext(char* P) { // 构造模式串 P 的 next 表
        size_t m = strlen(P), j = 0; // “主”串指针
        int* N = new int[m]; // next 表
        int  t = N[0] = -1; // 模式串指针
        while (j < m - 1)
            if (0 > t || P[j] == P[t]) { // 匹配
                j++; t++;
                N[j] = t; // 此句可改进为 N[j] = (P[j] != P[t] ? t : N[t]);
            }
            else // 失配
                t = N[t];
        return N;
    
    }
    
    用next数组匹配的代码如下:
    int match(char* P, char* S) { // KMP 算法
        int* next = buildNext(P); // 构造 next 表
        int m = (int)strlen(S), i = 0; // 文本串指针
        int n = (int)strlen(P), j = 0; //模式串指针
        while (j < n && i < m) // 自左向右逐个比对字符
            if (0 > j || S[i] == P[j]) // 若匹配,或 P 已移除最左侧
            {
                i++; j++;
            } // 则转到下一字符
            else
                j = next[j]; // 模式串右移(注意:文本串不用回退)
        delete[] next; // 释放 next 表
        return i - j;
    }
    
    时间复杂度分析

    再来看一下时间复杂度, 假设文本串长度为n,模式串长度为m。

    其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),但之前还要单独生成next数组,时间复杂度是O(m)(next数组的实现代码将在后续文章中继续讲解),所以整个KMP算法的时间复杂度是O(n+m)的。

    暴力的解法显而易见是O(n * m),所以「KMP在字符串匹配中极大的提高的搜索的效率。」


    1.jpg

    相关文章

      网友评论

          本文标题:KMP

          本文链接:https://www.haomeiwen.com/subject/hvgxuktx.html