js实现KMP算法,浅显易懂

开始

开始看了很多遍视频,一直一脸懵逼,然后看了几篇博客文章,一i边比较,一边自己码,总算理解了。

  • 首先,KMP算法是用来干什么的?用来匹配字符串,如果匹配,返回索引值
  • 其次,为什么要用KMP算法?因为能简化时间复杂度(废话,算法都是用来提升效率的)。
  • 然后,KMP算法是以什么方式简化时间复杂度的?
    • 一般我们匹配字符串可以用正则表达式,或者拿这个字符串与目标字符串一个个比较,那么就有一个问题,如果其中一个、最坏的是仅仅最后一个不匹配依然要从头开始匹配,这就很没必要,如:在这里插入图片描述
    • 而KMP算法,当某一个匹配失败后,通过一个next数组指导下一次匹配索引。展现出来的效果:在这里插入图片描述很明显,它没有回去从 b 开始匹配,由于前四个字符都一一匹配,且各不相同,直接跳至 a 和 e 比较,然后马上匹配到正确的字符串。
    • 在这里,模板字符串的前四个字符是各不相同的,所有一次跳四位,如果模板字符串在失配的字符前的字符有相同的呢?就要用到next数组。

next数组

  • 前缀后缀就不说了,只讲最大公共字符串长度
    • 例如一个模板字符串:ababac js实现KMP算法,浅显易懂_第1张图片
    • 为了适应代码和一些字符串方法,我求最大公共字符串长度方式可能有点不一样。比如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 '没找到';

你可能感兴趣的:(数据结构和算法,javascript,es6)