js实现KMP算法,浅显易懂
作者:互联网
开始
开始看了很多遍视频,一直一脸懵逼,然后看了几篇博客文章,一i边比较,一边自己码,总算理解了。
- 首先,KMP算法是用来干什么的?用来匹配字符串,如果匹配,返回索引值。
- 其次,为什么要用KMP算法?因为能简化时间复杂度(废话,算法都是用来提升效率的)。
- 然后,KMP算法是以什么方式简化时间复杂度的?
- 一般我们匹配字符串可以用正则表达式,或者拿这个字符串与目标字符串一个个比较,那么就有一个问题,如果其中一个、最坏的是仅仅最后一个不匹配依然要从头开始匹配,这就很没必要,如:
- 而KMP算法,当某一个匹配失败后,通过一个next数组指导下一次匹配索引。展现出来的效果:很明显,它没有回去从 b 开始匹配,由于前四个字符都一一匹配,且各不相同,直接跳至 a 和 e 比较,然后马上匹配到正确的字符串。
- 在这里,模板字符串的前四个字符是各不相同的,所有一次跳四位,如果模板字符串在失配的字符前的字符有相同的呢?就要用到
next
数组。
next数组
- 前缀后缀就不说了,只讲最大公共字符串长度
- 例如一个模板字符串:ababac
- 为了适应代码和一些字符串方法,我求最大公共字符串长度方式可能有点不一样。比如abab:就假设在后面这个b处失配时,它的前缀后缀最大公共字符串长度为1;比如ababac:就假设在 c 处失配时,它的前缀后缀最大公共字符串长度为3
- 最后由最大公共字符串长度值,组成数组next数组,最重要的是next数组的用法。
代码区
先看总体代码
const str = 'abababababac';
const mat = 'ababac';
/**
*
* @param {*} str 目标字符串
* @param {*} mat 模板字符串
*/
function kmp(str, mat) {
let commonMaxLen = 0; // 最大公共字符串长度
let next = [0, 0]; // 字符串的前两个字符的最大公共前缀长度直接赋值为0
let matLen = mat.length; // 模板字符串长度
let strIndex = 0; // str 中当前匹配的索引
let matIndex = 0; // mat 中当前匹配的索引
// 该循环求出 next 数组
for (let i = 2; i <= matLen - 1; i++) {
let temp = mat.slice(0, i)
for (let j = 1; j < temp.length - 1; j++) {
let tempSub1 = temp.slice(0, j)
let tempSub2 = temp.slice(temp.length - j, temp.length);
if (tempSub1 == tempSub2) {
commonMaxLen = tempSub1.length;
}
}
next[i] = commonMaxLen;
}
console.log('next: ', next); // [ 0, 0, 0, 1, 2, 3 ]
while (matIndex < matLen && strIndex < str.length) {
if (str[strIndex] == mat[matIndex]) { // 如果当前两个字符相等,移动至下一位
strIndex++;
matIndex++;
} else {
if(matIndex==0){ // 如果指示为元素为 0,目标字符串索引加1,从头开始匹配
strIndex+=1;
}else {
matIndex = next[matIndex]; // next 数组中的元素指示模板字符串哪个元素去匹配
}
}
}
if(matIndex==matLen){ // 匹配成功时,跳出while循环,模板字符串索引是等于它的长度的
return `该字符串位于 ${strIndex-matLen} 处`;
}
return '没找到';
}
next数组产生部分
- 无论哪个字符串,它的前两个字符的最大公共字符串长度都是0,因此上面直接赋值const next=[ 0, 0]。
- 利用slice方法,每次截取一个子字符串,然后循环,继续截取,比较是否相等,得到最大长度后,直接赋值给next数组。细节地方就是,按照我的方法看待最大公共字符长度,字符串最后一位是假设它失配,看它前缀后缀匹配个数就好。因此,slice方法不用截取模板字符串的最后一位。
// 该循环求出 next 数组
for (let i = 2; i <= matLen - 1; i++) {
let temp = mat.slice(0, i)
for (let j = 1; j < temp.length - 1; j++) {
let tempSub1 = temp.slice(0, j)
let tempSub2 = temp.slice(temp.length - j, temp.length);
if (tempSub1 == tempSub2) {
commonMaxLen = tempSub1.length;
}
}
next[i] = commonMaxLen;
}
字符串匹配部分
- 循环结束条件:如果模板字符串索引不小于(一般要么小于要么等于)它自身长度(最后一个字符匹配后,有个加1)结束循环:如果目标字符串的索引不小于它自身长度,结束循环;
- 判断条件:第一个 if 呢,如果当前两个字符匹配,两个索引都加一,然后再判断连续的下一个字符。else 不匹配时,再分两种情况:如果模板字符串的索引为 0,说明第一个就没有匹配成功;如果模板字符串的索引不为 0 ,说明前几次有匹配成功的(因为有个 ++ ),然后通过
next数组
中对应的索引元素指示下一次匹配使用的模板字符是哪一个。
while (matIndex < matLen && strIndex < str.length) {
if (str[strIndex] == mat[matIndex]) { // 如果当前两个字符相等,移动至下一位
strIndex++;
matIndex++;
} else {
if(matIndex==0){ // 如果指示为元素为 0,目标字符串索引加1,从头开始匹配
strIndex+=1;
}else {
matIndex = next[matIndex]; // next 数组中的元素指示模板字符串哪个元素去匹配
}
}
}
判断是否匹配成功
- 如果模板字符串索引值等于它自身的长度,说明是把它自身所有的字符完全匹配成功后才跳出。不然的话,它会被next数组赋值。
if(matIndex==matLen){ // 匹配成功时,跳出while循环,模板字符串索引是等于它的长度的
return `该字符串位于 ${strIndex-matLen} 处`;
}
return '没找到';
标签:字符,匹配,浅显易懂,next,索引,KMP,字符串,js,模板 来源: https://blog.csdn.net/qq_45209973/article/details/104843515