简单讲解KMP单模式匹配与AC算法多模式匹配(KMP篇)

前言

本篇是对于KMP单模式匹配以及AC算法多模式匹配的简单讲解,KMP算法与AC算法是关键字检索中的常见算法,能够快速而高效地查找出目标字符串中的多个关键字的匹配情况,而要检索的关键字通常被称为模式串,因此模式匹配四个字也就好理解了。网上的很多对于KMP的讲解总是结合了很多的数学公式,很多的晦涩难懂的专业词语,让人看了很头大,至少对于蠢笨的我来说,实在是一场煎熬,因此本篇的说明尽量做到通俗易懂,从逻辑以及思考方式的角度来对模式匹配的算法进行讲解。

 

一、KMP单模式匹配算法

1、综述

在描述多模式匹配算法之前,对于单模式匹配先进行一个简单的描述。单模式匹配与多模式匹配的不同点在于单模式匹配是搜索一个关键字,多模式匹配是搜索多个关键字。

         单模式匹配解决的是在长度为m的目标串(一个长字符串L)中查找长度为n的模式串(短字符串S)的问题,如果按照最粗糙的方法暴力求解,代码应该是这样的:

         for(inti=0; i

                   for(intj=0; j

                            //依次比较L[i] 与S[j],当完全相等的情况下记录

}

}

嗯,很不错的代码,思路清晰,结构明确,就是太慢了,时间代价为O(m*n)。于是情不自禁地想要快一点,这里就请出了KMP,KMP的核心思想就是比过的字符串就不比了,为了实现这一句话,KMP在模式串的基础上,加入了next表来进行实现。

因此KMP中需要说明的就是两点,一点是next表,另一点就是对比的方式(与暴力解法的区别在哪)。

2、next表

之前的描述中,KMP的核心在于六个大字:比过的,不比了。那么这个实现的方式就是通过next表。这里先举一个简单的模式串对应next表的例子:

j

1

2

3

4

5

6

7

pattern

a

b

c

a

b

c

d

next

0

1

1

1

2

3

4

         好的,这样一个表格的含义是什么呢,来猜一猜。首先是j,j的值从1-7,嗯,那就是编号?其次是pattern,pattern的意思是模式,那这个就是要检索的模式串了?接下来是next,好像之前提到了next表,那就可以大胆预测next就代表了next表!

         那么我猜对了吗?恭喜我自己,答对了!(好高兴)

         那好,既然每一行的名称的意义弄清楚了,接下来就是每一行的作用,第一行的j的作用很明确,就是编号,有一点要注意的是这里的编号的起始是1开始的而不是0。第二行的pattern是模式串,那就说明我们的模式串就是abcabcd。第三行的next代表的是next表,接下来要讲的就是它。

         讲next之前,先回忆一下暴力求解的时候,我们的流程是什么,先来一个目标串(要被检索的长字符串)abcdbcaaad,我们的模式串是abcabcd,这样首先从两个字符串的开始位置依次对比,当对比到第四个字符的时候,目标串的’d’与模式串的’a’不同,于是需要继续对比,将模式串adbadcd向后移动一位,将模式串的’a’与目标串的第二个字符’b’又开始对比,这样依次类推。这样做是完全正确的,思路清晰目标明确,只是太慢了而已,而慢的原因就在于每次对比失败时,模式串都只是向后移动一格,造成了很多重复的对比,无法实现“比过了不比了”这一伟大的宏观战略目标,为了解决这个问题,经典的KMP算法就出现了。

         那么现在看来next表确实非常关键,它能够实现我们“比过了不比了”这个伟大的目标。首先是讲一讲next表当中各个数字的含义和作用,例子当中的next表的值为0001230,其中每一个数字的含义是:当前字符之前的子字符串片段,从前向后数与从后往前数,能够相等的子字符串的最大长度加一。额,好像依然不能令人清晰它的含义,那接下来还是用几个例子来说明:对于字符串abcab的子字符串abca,从前往后数,可以得到a,ab,abc,从后往前数,可以得到a,ca,bca,明显可以看出来,最大的相等的子字符串就是a,长度为1,因此abcab的next值就应该为1+1=2,其实一眼也可以看出来,从前往后数最大也就有一个a和后面的a相等,就可以得到next值为2;再举一个例子,abc的子字符串ab,从前往后数有a,从后往前数有b,就是一个相等的都没有,那么就是它的next值就是0+1=1。所以到这里应该已经懂了,就重新说一遍之前的话:当前字符之前的子字符串片段,从前向后数与从后往前数,能够相等的子字符串的最大长度加一。这样的话,next表的每一个数值的含义与计算方法就已经明确了,接下来就是它的作用。

         重复地再说一次,next表的作用是“比过了不比了”,那么他每一个数字的作用就是记录了实现这一目标的方法。与传统的暴力解法不一样,当KMP遭遇到对比不一致的情况时,KMP不是简单地向后移动一格继续对比,而是将向后移动(j-next[j])的距离。依然以一个例子来说明,目标字符串是abadbcaaad,模式串为abcabcd,暴力解法中,对比到目标串的第三个字符时发现不一样,于是又从目标串的第二位开始对比。这里就是可以思考的地方了:既然目标字符串的aba已经和模式串的abc进行过对比了,那我们是不是就可以不比了呢?答案是肯定的,而不比了的含义就是,可以直接将模式串abcabcd移动到目标串的第四位’d’的位置(3-next[3]=3,移动3格)开始匹配对比。那这样突然就感觉很棒了,不再一个一个的进行对比,能够一次性地跨越已经比过的位置,但是并不是所有的目标串都是可以直接跳过所有已经对比的子字符串的,还需要考虑下面的这种情况:一个新的例子,目标串为abcabcabcd,模式串为abcabcd,首次对比中,对比到目标串的第7个字符’a’时发现了不一样,但是这里的移动如果直接移动到目标串第八位的位置’b’开始比较,明显就会错过abc(abcabcd)中括号括起来的部分,所以这里的移动是移动到目标串的第四个字符’a’的位置(7-next[7]=3,移动3格)进行对比。

3、总结

         OK,讲到这里next表的含义和使用方法也算是讲完了,而KMP算法也就主要实现了两点,其一是通过比过了不比了的思想直接跳过大段的字符串,另一个是通过next表确认当前字符前的重复数量来避免跳过过多的字符串。

         好了,今天就到这里了,搞定收工!

(最后说明一下,在网上与书中的资料中,j与next的数值的计算方法都有些微的差异,但是根本的目的都是在于记录j字符之前的子串的前后缀最大匹配数值,本文重在将模式匹配的逻辑与思考方式描述,不讨论具体细节。)

你可能感兴趣的:(算法)