0
点赞
收藏
分享

微信扫一扫

简述朴素模式匹配和KMP算法

是波波呀 2022-04-29 阅读 88
算法

需要了解的前提概念包括串,以及算法的应用场景
一. 串
串算是一种特殊的顺序表,只不过内部存储的元素是字符,一般用成对的单引号或双引号作为分界符号。
二. 要解决的问题
注:我们称供检索字符串为主串,称待检索字符串为模式串。

    对于一个主串,如果我们想在其中找到某一个字符串作为子串的位置(也就是这个子串的首字符的下标或者位序,看题目要求)。

三. 朴素模式串匹配算法的大致思想
最容易想到的就是,找出主串的所有子串,然后对主串的每个子串和模式串的每个字符逐一进行比较,只有比较循环体结束后,记录比较进度的标志位大于模式串长度,才能说明检索成功,因模式串扫描结束而完成了循环,说明找到了主串中第一个匹配的子串。
这一算法思考的方式简单,但时间复杂度很高,对于主串来说,每次对比出现不匹配时,主串上待匹配子串的位置只向前推进一个字符(相当于主串上指针在不停回溯),这当中会产生大量的无意义判断,这种算法需要优化。
四. kmp算法产生的一种设想
想象这样一个场景,当你在参加一次开卷考试,你在寻找一个经济学专有名词,一般等价物。如果你当前看到的书上的内容写着:货币是一种一般等价物。你是否会在看到主串中的货和模式串中的一不相同之后,再去判断模式串中的一是否和主串中的币相同?
应该不会吧,因为你是有记忆的,你记得前面几个字是压根和一般等价物不搭边的,也就是说你是有记忆的,不会对主串真的分出所有子串一个一个对比。
那么如何准确的表述这一个过程呢,或者如何将其归纳为一种算法思想呢。
设模式串长度为m,主串长度为n,当本轮匹配中,模式串中第k个字符的匹配发生了错误,此时就能说明,模式串指针前k–1个字符都和当前主串中指针位置处前面k-1个字符都能匹配的上,这时我们只要将主串第k个字符设为未知的字符,然后考虑如何针对前k-1个字符进行一种判断,让主串中指针不动,模式串指针适当回溯,最好能回溯到最佳位置,就能最大化利用时间,最好的模拟你的大脑检索信息时还会考虑记忆的这一过程。
我们要简化一个算法的计算时间,要么给它定义一套规则,让算法拥有判断哪些运算可以规避的能力,要么通过提前计算,传入一些中间结果,加速运算的过程(其实笔者认为二者的差别也不大)。
那么本质上我们要获取的信息,无非就是,模式串指针在不同位置匹配失败时,应当回溯的位置都是什么。
请注意,这样一来,很明显我们要给算法加入的中间结果或者说规避计算的规则,就和主串无关可,决定这一切的将只有模式串。(因为是中间结果和规则,而非直接传入最终结果)
那么我们很自然可以想到,定义一个和模式串等长的数组,每个数据代表着对应位序处模式串字符匹配失败时,指针应回溯到模式串的哪一位序处。(注意,这里全部使用位序,代表着模式串位序从1开始,算法实现要用下标,会有差异)。
这个数组就是next数组。
直接给出数组的手算方式。
(j设为模式串指针对应位序)
对于模式串j位序处匹配失败,
当j=1时,next[j]=0
当j=2时,next[j]=1
其他情况,next[j]=c
这里,c为模式串中前j-1个字符构成子串的最长的相等前后缀子串长度+1

    解释:
    1.首先这是还有优化空间的kmp算法,如,当模式串第一二字符相同,next[j]取1,仍旧浪费了一次无意义匹配,因为如果模式串第二位和当前主串i处不匹配,模式串第一位也不可能匹配(模式串一二位相同啊),后面会更新kmp优化的结果。并且,这里不是因为我直接对所有情况的next[2]直接写1导致的,可以尝试当把next[2]归为其他情况时,结果依旧是1。
    2.什么是前缀后缀。
    前缀指对这个串,不包括最后一个字符,但必须包括第一个字符的子串。
    后缀指对这个串,不包括第一个字符,但必须包括最后一个字符的子串。
    3.为啥要最长。
    因为如果不取最长,那么j可能会过度回溯,过度回溯就会导致直接跨过可能存在的正确子串,然后产生错误的运算结果。
    举个例子吧,比如模式串是aabaac,对于next[6]来说,它前面的6-1个字符构成的子串是aabaa,这个子串最长的相等前后缀就应该是aa,长度为2,根据定义,next[6]为3。它的实际含义就是,如主串中......aabaax,当指针i判断到x位置,模式串的j也到了c的位置,发现x和c匹配失败了,这时应当继续和x比较的,是模式串中位序为3的字符,也就是aax不是aac的话,还可能是aab,这样匹配就更符合人思考的逻辑;但如果不是最长的相等前后缀,这个前后缀被取成a的话,也就是next[6]写2,这时相当于aax如果不是aac,那就去看它会不会是aaa(第一个a没意义,后两个a相当于aabaac里的前两个a),如果主串中正好是aabaabaac,那么next[6]设2的结果就是整个子串匹配失败,但其实应当匹配成功,关键就在于模式串的过度回溯。(相当于是,朴素模式匹配中,模式串不动时,主串回溯不足的情况)
    本质上,最开始我们就是为了跳掉那些不可能匹配的地方,产生了找前缀的思想,然后为了让主串相对回溯不至于不足,我们就要尽量控制模式串实际回溯最大,也就是要找模式串k处k-1前串的最长相等前后缀。只有这样才能满足上述所有要求。
    优化待更新。
举报

相关推荐

0 条评论