【 算法 】KMP算法

本文详细介绍了KMP算法的工作原理,包括核心思想、next数组的生成及其在字符串匹配过程中的应用。通过构建next数组来确定模式串在不匹配时的回退位置,避免了不必要的回溯,从而提高了字符串匹配的效率。KMP算法的关键在于找到最长公共前后缀,以实现模式串的有效移动。文中还给出了具体的代码示例,展示了如何实现KMP算法。

在这里插入图片描述

KMP算法

  • KMP 算法可以做到仅仅后移模式串,比较指针不回溯

核心思想

  • 查找主串与模式串的最长公共前后缀
  • 将模式串中前缀的字符移动到后缀字符的位置
  • 继续往后匹配字符。

next数组

  • 前缀: 包含首位字符但不包括末位字符的子串
  • 后缀: 包含末位字符但不包括首位字符的子串
  • 最长公共前后缀
    • 当我们分析的字符串中有多个公共前后缀时,我们要取最长的公共前后缀。
    • 长度要小于错误位置左端子串长度的公共前后缀。
  • next数组
    • 当主串与模式串的某一位字符不匹配时,模式串要退回的位置
    • 记录最长公共前缀的长度

判断最长公共前缀

  • 判断前缀的下一个字符与后缀末尾字符之后的字符进行对比
  • 我们要用公共前缀的下一个字符,与我们公共后缀的下一个字符做比对
  • 如果它们是一样的,那么我们下一段字符的最长公共前后缀就是 当前最长公共前后缀 + 1
  • 定义 j 指向的就是我们公共前缀的末尾字符,定义 i 指向的就是我们公共后缀的末尾字符。
    • 当前最长公共前后缀长度 + 1 = j 所在下标 + 1
	j  : 0  1  2  3  4  5  6  7  8  9  10  11
    p  : A  B  A  B  A  A  A  B  A  B  A   A	

    下标0
        A
        无最长公共前缀
        next[0] = 0
    下标1
        AB
        无最长公共前缀
        next[1] = 0
    下标2
        ABA
        最长公共前缀:A
        next[2] = 1
    下标3
        ABAB
        最长公共前缀:AB
        next[3] = 2
    下标4
        ABABA
        最长公共前缀:ABA
        next[4] = 3
    下标5
        ABABAA
        最长公共前缀:A
        next[5] = 1
    下标6    
        ABABAAA
        最长公共前缀:A
        next[6] = 1
    下标7   
        ABABAAAB
        最长公共前缀:AB
        next[7] = 2
    下标8   
        ABABAAABA
        最长公共前缀:ABA
        next[8] = 3
    下标9  
        ABABAAABAB
        最长公共前缀:ABAB
        next[9] = 4
    下标10
        ABABAAABABA
        最长公共前缀:ABABA
        next[10] = 5
    下标11   
        ABABAAABABAA
        最长公共前缀:ABABAA
        next[11] = 6

prefix前缀数组 / PMT 数组

    [0,0,1,2,3,1,1,2,3,4,5,6]

prefix数组减去 1 就是 next数组

匹配步骤

  • 定义 i j 从下标0开始
  • 循环直到 i 到达我们主串的长度
  • 匹配成功
    • 模式串 中的 j 指向的字符与 原串 中 i 指向的字符相同时
    • i 和 j 就都可以往右挪动一位,往后继续匹配了。
  • 把公共前缀挪动到公共后缀的位置,j = next [ j ]
  • 找主串中的下一个字符与我们模式串的下标 0 的字符进行匹配了,所以这里就是 ++i 即可。
  • 最后判断 j 是否到达模式串最后
    在这里插入图片描述

代码

function KMP(source, str) {
    // 定义数组
    const next = new Array(str.length).fill(0);
    // next数组
    {
        var i = 1, j = 0;
        while (i < str.length - 1) {
            // 如果匹配成功 那么最长公共前缀长度 j + 1
            if (str[i] === str[j]) {
                j++;
                i++;
                next[i] = j;
            }
            // 匹配失败 j 指针回溯到 0 位置
            else {
                if (j > 0) {
                    j = next[j];
                } else {
                    i++;
                }
            }
        }
        // 0号位置不放置字符
        next[0] = -1;
        console.log(next)
    }

    // 主串匹配
    {
        let i = 0,
            j = 0;

        while (i < source.length) {
            if (str[j] === source[i]) {
                ++j, ++i;
            } else {
                if (j > 0) {
                    j = next[j];
                } else {
                    ++i;
                }
            }
            if (j === str.length)
                return true;
        }
        return false;
    }
}

console.log(KMP('ABABABAABABAAABABAA', 'ABABAAABABAA'));
[
   -1, 0, 0, 1, 2,
   3, 1, 1, 2, 3,
   4, 5
]
true

console.log(KMP('hello', 'll'));
[-1, 0]
true

console.log(KMP('HaHaHa', 'XiXiXi'));
[-1, 0, 0, 1, 2, 3]
false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值