之前的字符串匹配使用暴力拆解的办法,使用两个for循环来慢慢匹配,最坏时间复杂度为O(n*m)
AB是重复出现的内容,而且已经比较过,是在目标字符串中存在的
再从对应的位置进行开始比较,减少重复比较的次数
KMP算法就是利用已经比较过的内容,有重复的直接移动到重复的位置,没有重复的,直接比较下一个
而字符串位置的移动是根据部分匹配表来实现
部分匹配表的实现
部分匹配表根据匹配的字符串进行拆解
以字符串“ABCDABD”为例:
"A"的前缀和后缀是空集,为0
"AB"的前缀[A]和后缀[B],共有元素的长度为0
"ABC"的前缀[A, AB]和后缀[BC, C],共有元素的长度为0
"ABCD"的前缀[A, AB, ABC]和后缀[BCD, CD, D],共有元素的长度为0
"ABCDA"的前缀[A, AB, ABC, ABCD]和后缀[BCDA, CDA, DA, A],共有元素的长度为1
"ABCDAB"的前缀[A, AB, ABC, ABCD, ABCDA]和后缀[BCDAB, CDAB, DAB, AB, B],共有元素的长度为2
"ABCDABD"的前缀[A, AB, ABC, ABCD, ABCDA, ABCDAB]和后缀[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0
部分匹配表为[0, 0, 0, 0, 1, 2, 0]
出现不匹配时,已经成功比较的字符串个数为6
部分匹配表第6个为2
匹配字符串向右移动6 - 2 = 4个位置
移动位数 = 已匹配的字符数 - 对应的部分匹配值
移动后再从当前的位置比较下去
用JavaScript实现KMP算法
function kmp(content, target) {
const length = target.length;
const arr = [];
// 获取部分匹配值
for (let i = 0; i < length; i++) {
const temp = target.slice(0, i + 1);
if (temp.length === 1) { // 长度为1,没有前缀和后缀
arr.push(0);
} else if (temp.length === 2) { // 长度为2,直接比较前后
if (temp[0] === temp[1]) {
arr.push(1);
} else {
arr.push(0);
}
} else { // 多前后缀比较,获取最长的相同长度
const a1 = [];
const a2 = [];
let flag = true;
for (let j = 0; j < temp.length - 1; j++) {
a1.push(temp.slice(0, j + 1));
a2.push(temp.slice(j + 1));
}
for (let g = 0; g <= a1.length - 1; g++) {
if (a1[a1.length - 1 - g] === a2[g]) {
arr.push(a2[g].length);
flag = false;
break;
}
}
if (flag) {
arr.push(0);
}
}
}
const result = []; // 匹配结果
let i = 0;
let r = 0;
while (i < content.length) {
if (content.charAt(i) === target.charAt(r)) { // 当前字符相等
r++;
i++;
if (r === target.length) { // 相等字符数量与目标字符串长度相等
result.push(i - r);
r = 0;
}
} else { // 当前字符不相等
if (r !== 0) { // 已有比较相等的字符,进行部分匹配移动位置
const move = arr[r - 1];
r = r - (r - move);
} else {
i++;
}
}
}
return result;
}
参考连接: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html