参考链接
阮一峰老师的字符串匹配的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]--->A与pattern[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
}
}
本篇完...