I. KMP算法概述
KMP是一种字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,并由他们名字命名K
nuth,M
orris,P
ratt。因此得名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;
}