KMP算法(字符串匹配)

Posted 前端精髓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法(字符串匹配)相关的知识,希望对你有一定的参考价值。

字符串匹配是常见的算法题,就有一个字符串判断里面是否包含另一个字符串。

举例来说,有一个字符串"AAAAAABC"(主串),我想知道,里面是否包含另一个字符串"AAAB"(模式串)?对主串和模式串做匹配。


首先,字符串 “AAAAAABC” 的第一个字符与搜索词 “AAAB” 的第一个字符,进行比较。

AAAAAABC
AAAB

字符串有一个字符与搜索词的第一个字符相同,接着比较字符串和搜索词的下一个字符,还是相同。直到字符串有一个字符,与搜索词对应的字符不相同为止。

当字符串的索引为 3 的时候发现不相等,这时,最自然的反应是,将搜索词整体后移一位,再从头逐个比较。

AAAAAABC
 AAAB

基于这个想法我们可以得到以下的程序:

function bf(ts, ps) 
  let t = ts;
  let p = ps;
  let i = 0; // 主串的位置
  let j = 0; // 模式串的位置
  while (i < t.length && j < p.length) 
    if (t[i] === p[j])  // 当两个字符串相同,就比较下一个
      i++;
      j++;
     else 
      i = i - j + 1; // 一旦不匹配,i后退
      j = 0; // j归0
    
  
  if (j === p.length) 
    return i - j
   else 
    return -1;
  


console.log(bf('AAAAAABC', 'AAAB'))

上面的程序是没有问题的,但不够好!这是暴力解法复杂度 O(nm) 的。这太慢了!

我们很难降低字符串比较的复杂度(因为比较两个字符串,真的只能逐个比较字符)。因此,我们考虑降低比较的趟数。

跳过不可能成功的字符串比较

有些趟字符串比较是有可能会成功的;有些则毫无可能。而如果我们跳过那些绝不可能成功的字符串比较,则可以希望复杂度降低到能接受的范围。

一个基本事实是,当 d 不匹配时,你其实知道前面五个字符是"abcab"。如果是人为来寻找的话,肯定不会再把 i 移动到索引为1,我们会直接移动到索引为3!就可以来到第二个"ab"的位置。

所以,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道指针要移动到哪?

移动位数 = 已匹配的字符数 - 对应的部分匹配值

首先,要了解两个概念:“前缀"和"后缀”。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

“abcab"的前缀为[a, ab, abc, abca],后缀为[bcab, cab, ab, b],共有元素为"ab”,长度为2;

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,“abcab"之中有两个"ab”,那么它的"部分匹配值"就是2("ab"的长度)。搜索词移动的时候,第一个"ab"向后移动到索引为3(字符串长度5-部分匹配值2),就可以来到第二个"ab"的位置。

function getNext(p) 
  let nxt = [];
  nxt.push(0); // next[0] 必然是0
  let x = 1; // 因此 nxt[1] 开始求
  let now = 0;
  while (x < p.length) 
    if (p[now] === p[x])  // 如果 p[now] == p[x] ,则可以向右扩展一位
      now += 1
      x += 1
      nxt.push(now)
     else if (now) 
      now = nxt[now - 1] // 缩小 now,改成 nxt[now - 1]
     else 
      nxt.push(0) // now 已经为0,无法再缩小了, 故 nxt[x] = 0
      x += 1
    
  
  return nxt


console.log(getNext('abcab'))
// [ 0, 0, 0, 1, 2 ]

根据nxt数组移动标尺。

function bf(ts, ps) 
  let t = ts;
  let p = ps;
  let i = 0; // 主串的位置
  let j = 0; // 模式串的位置
  let nxt = getNext(ps)
  while (i < t.length && j < p.length) 
    if (t[i] === p[j])  // 当两个字符串相同,就比较下一个
      i++;
      j++;
     else 
      // 失配了
      if (j) 
        j = nxt[j - 1] // 根据nxt数组移动标尺
       else 
        i++; // ps[0]失配了,直接把标尺往右移动一位
      
    
  
  if (j === p.length) 
    return i - j
   else 
    return -1;
  

以上是关于KMP算法(字符串匹配)的主要内容,如果未能解决你的问题,请参考以下文章

KMP算法的理解和代码实现

字符串匹配算法:KMP(The Knuth-Morris-Pratt Algorithm)

KMP算法

KMP算法

字符串匹配(KMP)算法及Java实现

搞定kmp算法