关于"从头到尾彻底理解KMP(2014年8月22日版)"的个人理解

来源:互联网 发布:故宫淘宝是什么 编辑:程序博客网 时间:2024/06/04 19:09

从头到尾彻底理解KMP(2014年8月22日版)

1,此文先求解next数组

这里先从直观上来理解,即考虑模式串P的“各个子串的前缀后缀的公共元素的最大长度”容易得到,然后通过右移各个子串的前缀后缀的公共元素的最大长度数组,然后p0补-1得到next数组。

之后通过代码求解next数组的各个元素的值。

这里需要弄明白为什么需要递归,为什么可以递归求解?

递归通过关键代码

<span style="font-size:14px;">k=next[k]</span>
实现。因为我们不希望用最笨、最原始、浪费时间、浪费感情的回溯法来进行模式串和目标串的匹配,所以用到了next数组来帮助我们找到一个合适的、合理的、恰当的目标串的相同前缀后缀位置,这就是next数组存在的意义。

而如果当前p[j]位置的元素!=p[k]位置的元素了,那么我们需要知道从紧邻p[j]位置之前符合要求的后缀串中再找到最长匹配后缀串,而紧邻p[j]位置之前符合要求的后缀串紧邻p[k]位置之前符合要求的后缀串是一样的,所以我们可以将问题转化为从紧邻p[k]位置之前符合要求的后缀串中再找到最长匹配后缀串,这样就形成了递归的形式。

同时也回答了“为什么需要递归,为什么可以递归求解?这两个问题

2,然后对next数组进行优化

这里首先要理解不能允许p[j] == p[ next[j] ]的含义:之前我们都在讨论next数组的求解问题,这里问题升华了,我们要做到避免p[j] (模式串)和 s[i](目标串) 匹配失败的情况,这样就少了很多不必要的操作步骤,从而节省了时间,提高了效率。

我们用原文中的例子来解释这个事情:

比如,如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当它跟下图中的文本串去匹配的时候,发现b跟c失配,于是模式串右移j - next[j] = 3 - 1 =2位。

    右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知p[3] = b,与s[3] = c失配,而右移两位之后,让p[ next[3] ] = p[1] = b 再跟s[3]匹配时,必然失配。问题出在哪呢?

   

    问题出在不该出现p[j] == p[ next[j] ]。为什么呢?理由是:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配),所以不能允许p[j] = p[ next[j ]]。如果出现了p[j] = p[ next[j] ]咋办呢?如果出现了,则需要再次递归,即令next[j] = next[ next[j] ]。

因为原next数组的求解算法中,未考虑p[j] == p[ next[j] ]的情况,而是直接令next[j] = k,虽然算法没有错误,但是这里是可以改进的,即将p[j] (模式串)和 s[i](目标串) 匹配失败而引起的递归的情况在next数组构建时提前考虑到,也就是通过改进next数组元素的值来节省后续的很多步骤,提高了效率。后来的时间复杂度可以说明KMP效率还是很高的~

3,将next数组用于目标串的匹配

这里就没什么好说的。

4,分析KMP算法的时间复杂度

有了算法代码,容易得到结果。

5,介绍两种扩展算法

BM算法虽然一直都在说模式串怎么怎么样,实质还是基于模式串和目标串的匹配对比操作,无论是坏字符还是好后缀都是这样,但是这个算法确实构思巧妙,很是牛逼!

该算法从模式串的尾部开始匹配,且拥有在最坏情况下O(N)的时间复杂度。在实践中,比KMP算法的实际效能高。

我们已经介绍了KMP算法和BM算法,这两个算法在最坏情况下均具有线性的查找时间。

但实际上,KMP算法并不比最简单的c库函数strstr()快多少,而BM算法虽然通常比KMP算法快,但BM算法也还不是现有字符串查找算法中最快的算法,比BM算法更快的查找算法即Sunday算法。

0 0