阮一峰老师的字符串匹配的KMP算法
kmp算法详解(最透彻的没有之一!)javascript
KMP
算法的原理部分,请看阮一峰老师的字符串匹配的KMP算法,这里主要是代码实现。html
假设给定模式串ABABCABAA
,要求出该模式串的最大长度表。java
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
数组的含义是除当前字符外的最长公共先后缀的长度。spa
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
的公共先后缀。.net
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
算法,但仍是存在许多优化的地方。
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
}
}
复制代码
本篇完...