KMP算法分享

813 阅读3分钟

本文是前端内部分享,参考文章

1. 什么是KMP

一种改进的字符串匹配算法

2. 暴力匹配法

目标字符串(上面)和模式串(下面)一个接一个的对比

      i
A B C A B D A B C E A B D
A B C E
      j               

当第一次检测到不相同以后

  i
A B C A B D A B C E A B D
  A B C E
  j               

3. 如果是人来处理,会怎么做?

      i
A B C A B D A B C E A B D
      A B C E
      j               

例2

          i
A B C A B C D H I J K
A B C A B B
          j

移动

          i
A B C A B C D H I J K
      A B C A B B
          k

特点:

  • i不回溯,只需要调整j的位置

5.png

公式:P[0~k-1] == P[j-k~j-1]

(k为下一次跳转的位置)
模式字符串的前k个等于j所在位置的前k个

KMP算法的核心 找到左右的k(j跳转的下一个位置)

4. 获取下一个跳转位置的代码

思路:

  • 设置双指针,j指针依次指向每一个元素,通过k指针计算j可以跳转的位置
  • 当k指针与j指针相等时,两个指针同时相加;否则移动k指针到它可以移动的位置
  • 每一次比较都是为了定 j + 1应该跳转的位置
// 获取下一个要跳转的位置
function getNext (ps) {
  var next = []
  next[0] = -1 // 初始化为-1
  let j = 0
  let k = -1
  while (j < ps.length - 1) {
    if (k == -1 || ps[j] == ps[k]) {
      next[j + 1] = k + 1 // 如果ps[j]==ps[k],那么j+1跳转的位置就是k+1
      j++
      k++
    } else {
      // 如果 ps[j] 和 ps[k] 不相等,要想求 j + 1的跳转值,先将指针移到next[k]的位置,
      // 如果k移动到next[k]后,ps[j] == ps[k],那么立马就可以得出 next[j + 1]的位置
      // 如果和第一个元素比较以后还是不相等,那么 k = next[k] 将等于 -1
      k = next[k]
    }
  }
  return next
}

举例

              j
a b c d a b c a b
      k

// [ -1, 0, 0, 0, 0, 1, 2, 3, 1]

5. KMP算法

// ts 主串;ps 子字符串
function KMP (ts, ps) {
  let i = 0 // 主串的位置
  let j = 0 // 模式串的位置
  let next = getNext(ps) // next只与ps有关
  while (i < ts.length && j < ps.length) {
    if (j == -1 || ts[i] == ps[j]) {
      // 当j为-1时,要移动的是i,当然j也要归0
      i++
      j++
    } else {
      j = next[j] // j回到指定位置,如果赋值后等于0,那么就比较ps[0]与ts[j]是否相等
    }
  }
  if (j == ps.length) {
    return i - j // 返回ps第一个字符所在的位置,i此时的位置是ps的最后一个字符所在的位置
  } else {
    return -1
  }
}

举例

// 第一个不相同,i++
i
a b c d a b c a b
b c d
j


// ts[i]和ps[j]相同两个就同时相加
  i
a b c d a b c a b
  b c d
  j
  
      i
a b c d a b c a b
  b c d
      j

6. 为什么可以不回溯

  1. 如果回溯后,不能保证前面的元素全部匹配就没有意义;
  2. 如果回溯后,前面的内容全部匹配了,那么肯定可以通过移动j的方式实现;

7. 总结

KMS算法就是首先找到模式串当每一个字符不匹配可以跳转的位置next,然后在与目标字符串进行比较时,如果不相同就将模式串的指针重新指向next对应的位置,然后继续比较。