I. KMP算法概述
KMP是一种字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,并由他们名字命名Knuth,Morris,Pratt。因此得名KMP。
KMP算法的是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。其时间复杂度为O(m+n)。
II. KMP算法原理
KMP算法,是在暴力算法的基础上,减少重复匹配,来实现的。先看下暴力匹配的过程,以及暴力匹配的问题。
有如下主串,需要匹配模式串在主串中的位置
主串:a,b,a,b,a,b,c,a,a
模式串:a,b,a,b,c
通过暴力匹配的方式如下:
从图中可以看到,在失配的情况下,主串需要进行回溯再进行匹配。未利用已经匹配过的数据信息,减少匹配。而KMP则可以避免主串回溯。
从中,可以看出通过KMP可以减少匹配次数。
KMP中模式串下方的数组,用来在失配的时候,标识模式串匹配的下标,即回溯的位置,称之为next数组。而其计算的过程为预处理阶段,后续的模式匹配称为搜索阶段。
A. 预处理阶段
next数组主要用于控制模式串的回溯位置,如果模式串有相同项,利用该信息,就可以减少重复的回溯。
主串和模式串,在第四位,出现失配的情况。模式串前两位ab和开头的ab相同,长度为2。那么主串的前四位也是如此。利用该信息,就可以减少模式串的回溯,直接从第二位开始匹配即可。因此,通过匹配字符串前面相等前后缀的方式,得到不同索引下的回溯长度。
当搜索第0位时,0位之前不存在,设置为 -1。其他位为匹配前面的字符串最长相等前后缀
| 索引 | ||||
|---|---|---|---|---|
| 0 | null | |||
| 1 | a | |||
| 2 | a | b | ||
| 3 | a | b | a | |
| 4 | a | b | a | b |
索引为0,之前不存在字符串,长度设置为 -1
索引1,之前字符串a
由于前后缀都为0。因此,最长相等前后串长度0
索引2,之前字符串a,b
前缀:['a']
后缀:['b']
由于前后缀不相等。因此,最长相等前后串长度0
索引3,之前字符串a,b,a
前缀:['a','a,b']
后缀:['a','b,a']
由上可以看出,最长相等前后串长度1
索引4,之前字符串a,b,a,b
前缀:['a','a,b','a,b,a']
后缀:['b','a,b', 'b,a,b' ]
由上可以看出,最长相等前后串长度2
这个时候,得到next数组为[-1,0,0,1,2],由于索引0 ,之前字符串不存在,索引 1 ,之间只有1个字符串。因此,next数组以[-1,0]开头。
下面看下代码中,next数组如何生成的。
function getNext(p) {
let next = [];
let j = 0; // 前缀末尾
next[0] = -1; // 0之前没有字符串,长度为 -1
next[1] = 0; // 1 之前,前后缀最长长度为 0
// i 后缀末尾
for (let i = 1; i < p.length - 1; i++) {
// 不匹配
while (j != 0 && p[i] !== p[j]) {
j = next[j]
}
// 匹配
if (p[i] == p[j]) {
++j;
}
next[i + 1] = j
}
return next;
}
B. 搜索阶段
在获取到next数组后, 在失配的时候根据next数组值,进行模式串回退处理,主要流程如下
III. KMP算法实现
A. Javascript实现
function kmp(mainString, pattern) {
let next = getNext(pattern);
let i = 0;
let k = 0
while (i < mainString.length) {
if (pattern[k] === mainString[i]) {
k++;
i++;
}
else {
if (k > 0) {
k = next[k]
}
else {
i++
}
}
if (k === pattern.length - 1) {
console.log("找到索引" + (i - k));
k = next[k];
}
}
}
function getNext(p) {
let next = [];
let j = 0; // 前缀末尾
next[0] = -1; // 0之前没有字符串,长度为 -1
next[1] = 0; // 1 之前,前后缀最长长度为 0
// i 后缀末尾
for (let i = 1; i < p.length - 1; i++) {
// 不匹配
while (j != 0 && p[i] !== p[j]) {
j = next[j]
}
// 匹配
if (p[i] == p[j]) {
++j;
}
next[i + 1] = j
}
return next;
}