javascript实现KMP算法

3,838 阅读3分钟

参考链接

阮一峰老师的字符串匹配的KMP算法
kmp算法详解(最透彻的没有之一!)

KMP算法的原理部分,请看阮一峰老师的字符串匹配的KMP算法,这里主要是代码实现。

构建最大长度表

假设给定模式串ABABCABAA,要求出该模式串的最大长度表。

i 子串 最长前后缀 最长公共前后缀长度
0 A / 0
1 AB / 0
2 ABA A 1
3 ABAB AB 2
4 ABABC / 0
5 ABABCA A 1
6 ABABCAB AB 2
7 ABABCABA ABA 3
8 ABABCABAA A 1

很容易理解最大长度表每个值的含义是模式串子串str[0,i]的最长公共前后最缀长度。 现在,我们来回顾一下这个表是怎么得来的?
假设我们目前得到的表是下面这个样子的,最后两个是未知的,也是待求解的

i 0 1 2 3 4 5 6 7 8
pattern[i] A B A B C A B A A
len 0 0 1 2 0 1 2 ? ?

我们已经知道i=6的值是2,对应的模式串字符为A,那么我们如何得到i=7的值呢?


我们只需要将pattern[len]pattern[i]进行比较即可,此时,我们得到pattern[len]===pattern[i],然后对len++,记录prefix_table[i]===len,i指针后移即可。

i 0 1 2 3 4 5 6 7 8
pattern[i] A B A B C A B A A
len 0 0 1 2 0 1 2 3 1

现在,我们已经得到了i=7的值,还剩下i=8。我们同样按照上面的思路来思考。 比较pattern[8]--->Apattern[3]---->B,出现失配的情况,那此时我们应该怎么做呢,我们的思路是缩短公共前后缀的长度,使prefix_table发生侧移。

代码如下:

function generatePrefixTable(pattern){
    var prefix_table = [];
    var len = 0;// 最长公共前后缀长度初始化为0
    prefix_table[0] = len;
    var i = 1;
    var n = pattern.length;
    while(i<n){
        if(pattern[len] === pattern[i]){
            len++;
            prefix_table[i] = len;
            i++;
        }else{
            if(len>0){ // 侧移
                len = prefix_table[len-1]; //缩小最长公共前后缀的长度
            }else{ // 侧移移动到len=0时,pattern[len]!=pattern[i],即pattern[0]!=pattern[i]
                prefix_table[i] = 0;
                i++;
            }
        }
    }
    return prefix_table;
}

构建next数组

function generateNextArr(prefix_table){
    for(var i=prefix_table.length-1;i>0;i--){
        prefix_table[i] = prefix_table[i-1]
    }
    prefix_table[0] = -1;
}

next数组的含义是除当前字符外的最长公共前后缀的长度。

i 0 1 2 3 4 5 6 7 8
pattern[i] A B A B C A B A A
next数组 -1 0 0 1 2 0 1 2 3

比如对于ABABCABAA来说,pattern[2]之前的字符串AB中有长度为0的公共前后缀,所以next[2]=0;pattern[8]之前的字符串ABABCABA中有长度为3的公共前后缀。

kmp搜索算法的实现

    function kmp(str,pattern){
        var prefix_table = generatePrefixTable(pattern)
        generateNextArr(prefix_table)
        var i=0; // str 指针
        var j=0; // pattern指针
        while(i<str.length && j< pattern.length){
            if(str[i]===pattern[j]){
                i++;
                j++;
            }else{
                j = prefix_table[j] // 右移
                if(j===-1){
                    i++;
                    j++;
                }
            }
        }
        if(j===pattern.length){
            return i-j
        }else{
            return -1
        }
    }

算法测试

    kmp("bbc abcdab abcdabcdabde", "abcdabd")  // 结果输出为15,正确

KMP算法优化

到目前为止,已经实现了基本的kmp算法,但还是存在许多优化的地方。

  • 在上面我们是先构建最大长度表,然后构建next数组,其实我们可以直接构建next数组,节约时间和空间。
    function generateNextArr(pattern){
        var i = 0;
        var j = -1;
        var next = []
        next[0]=-1
        while(i<pattern.length){
            if(j===-1||pattern[i]===pattern[j]){
                i++;
                j++;
                next[i]=j
            }else{
                j = next[j]
            }
        }
        return next;
    }
  • 对于上面的kmp函数,我们可以改写成下面的形式
  function kmp(str,pattern){
        var next = generateNextArr(pattern)
        var i=0; // str 指针
        var j=0; // pattern指针
        while(i<str.length && j< pattern.length){
            if(str[i]===pattern[j] || j===-1){
                i++;
                j++;
            }else{
                j = next[j] // 右移
            }
        }
        if(j===pattern.length){
            return i-j
        }else{
            return -1
        }
    }

本篇完...