简介
KMP算法,全名Knuth-Morris-Pratt算法,是以其三个发布者命名的用于在字符串中查找子串的算法。
KMP算法事实上是一种由暴力匹配改进而来的算法,它可以在O(M+N)的时间复杂度和O(N)的时间复杂度下解决在字符串中查找字串的问题。其中心思想就是利用前缀函数求得的最长相等真前缀和真后缀长度来跳过部分匹配项目
概念解释
后缀
后缀 是指从某个位置开始到整个串末尾结束的一个特殊子串。
真后缀 真后缀是指除了字符串本身的后缀
举例来说,字符串 abcabcd 的所有后缀为 {d, cd, bcd, abcd, cabcd, bcabcd, abcabcd},而它的真后缀为 {d, cd, bcd, abcd, cabcd, bcabcd}。
前缀
前缀 是指从串首开始到某个位置结束的一个特殊子串。
真前缀 指除了字符串本身的的前缀。
举例来说,字符串 abcabcd 的所有前缀为 {a, ab, abc, abca, abcab, abcabc, abcabcd}, 而它的真前缀为 {a, ab, abc, abca, abcab, abcabc}。
前缀函数
给定一个长度为的字符串,其前缀函数被定义为一个长度为的数组。其中定义是
- 如果子串有一对相等的真前缀与真后缀,那么就是这个相等真前缀与真后缀的长度。
- 如果不止有一对,那么就是其中最长一对的长度。
- 如果没有相等的,那么
简单来说就是最长的相等的真前缀与真后缀的长度
KMP算法对比暴力求解
暴力求解
function getSubstrForce(haystack,needle) {
let length = haystack.length
let j = 0;
let endLength = needle.length
for( let i = 0; i < length; i++ ) {
while(haystack[i] === needle[j]) {
i++;
j++
}
if(j === endLength) {
return i - j
} else {
i -= j - 1
j = 0
}
}
}
KMP求解
我们分两种情况分析字符串匹配的过程,来优化暴力求解中存在的问题。
模式串中无重复
这种情况我们还以目标串ABCABDABCEABD和模式串ABCE为例,当第一次匹配失败时,我们可以发现A其实不必逐个从A->-B->C尝试,因为A和E中间已经匹配,肯定不是A,因为目标串不需要回退
模式串中有重复
模式串中ABCAB均已匹配成功但是E与A不匹配,在这种情况下目标串仍不用回退,只要模式串开头的AB和目标串当前结尾的AB对齐,就可以从C中开始比较,又跳过了暴力比较中的两次比较。可以明显看出,模式串应回退截止已匹配字符串最大相等真前缀和真后缀的长度。
那么问题就移动到如何高效的求的最大相等真前缀与真后缀,也就是前缀函数如何生成
前缀函数生成
暴力求法
function getPrefixFun(input) {
const length = input.length;
let prefix = new Array(length).fill(0)
for(let i = 1; i < lengthl; i++) {
for(let j = i; j >= 0; j--) {
if(input.substr(0,j) === input.substr(i - j + 1,j)) {
prefix[i] = j
break;
}
}
}
return prefix
}
优化
当我们发现的时候我们要找到一个最大长度j使得,这时候观察我们可以发现j也是的相等真前后缀,所以可以得到,所以我们可以得到j就是处的前缀值,也是
const getNext = (input) => {
const length = input.length
let next = new Array(length).fill(0)
for(let i = 1; i < length; i++) {
let j = next[i - 1]
while(j > 0 && input[i] !== input[j]) {
j = next[j - 1]
}
if(input[i] === input[j])j++
next[i] = j
}
return next
}
完整代码
var strStr = function(haystack, needle) {
const getNext = (input) => {
const length = input.length
let next = new Array(length).fill(0)
for(let i = 1; i < length; i++) {
let j = next[i - 1]
while(j > 0 && input[i] !== input[j]) {
j = next[j - 1]
}
if(input[i] === input[j])j++
next[i] = j
}
return next
}
let j = 0;
let next = getNext(needle)
for(let i = 0; i < haystack.length; i++) {
while(j > 0 && haystack[i] !== needle[j]) {
j = next[j-1]
}
if(haystack[i] === needle[j]) {
j++
}
if(j === needle.length) {
return i - j + 1
}
}
return -1
};