写在前面:
最近在看王争老师的《数据结构与算法之美》,看到了字符串匹配算法,发现里面门道挺多,光是想搞明白点就花了几天功夫,鉴于我付出的精力,必须好好记录一下。
因为目前只看了单模式匹配,所以只说BF,RM,BM,KMP四个算法。
本文不会说代码实现,而是尽量理清思路,从以下几个方面入手:有什么问题?可以怎么做?为什么这么做就可以了?
先来个题目背景,在stringA中找stringB,A为主串,B为模式串,假设主串长度为n,模式串长度为m,n>m。
BF
BF暴力匹配,这个大家都清楚,最简单也是最耗时的。先一一对比,出错了就"后移1位"再从头开始对比。

这种"从头再来"的做法极大提升了算法的时间复杂度。
具体为:单次对比耗时O(m),最多进行m-n+1次对比,考虑到一般n都很大于m,所以最终复杂度简化为O(mn)。
RM
在BF算法中,主串的子串与模式串的对比时间复杂度为O(m),相当麻烦,如何快速对比两者呢?
RM算法引入了hash思想解决这一问题(hash思想:不定长的输入得到定长的输出(散列值),散列值对不上就一定不同,对上了也不一定相同,需要做额外的确认)。
先遍历一次主串,计算其子字符串的hash值,然后再和模式串的hash值对比,hash一致后再进行原值对比。

对比环节很简单,算法的关键在于计算hash环节。因为计算一个子字符串就可能需要O(m)的时间,不优化,时间复杂度就和BF差不多了。
所以该如何快速的得到随机分布的hash值呢?
这里举个例子,假设主串26572,模式串是657,假设计算hash的方法如下:

572的hash值为 510 10+710+2,其实计算572的hash时,可以利用657的计算结果,hash(572)=(hash(657)-610*10)*10+2
别看现在两者的计算量相差无几,当模式串一长,这种"递推式"的计算方式的时间复杂度为还能保持在O(1)!因此RM的时间复杂度为O(n)
BM
BM算法的核心在于两个原则:"坏字符"和"好后缀",
"坏字符"
"坏字符",顾名思义,便是比对不一致的字符。

BF算法遇到这个问题只能"滑动一步",但其实,根据模式串提供的信息,我们可以移动很多步(这里需要补充一点,无论是BM,还是KMP算法,其核心要义都是找到能"多滑动几位"的规则,这样便能快速的排除错误,排除的越多,离终点越近)。
如果在模式串找到该字符(如果有多个,取最右侧的那个),滑动2格消除"坏字符"。
为什么可以滑动2格呢?因为a在d前2格,只滑动一格,"坏字符"依旧存在

这里提一句,虽然在图中是模式串往后滑了,其实更恰当的理解是:模式串不动,主串往前滑使得"对比项"后移("对比项"aca往后移2位,变成了abd),这样更能体现"排除错误"的思想。
如果找不到,那可以移动更多位数

前文中提到了"选最右侧字符",这是为了防止错漏正确答案,可因为选最右侧字符的关系,所以可能会产生倒退,如下图。

因此如果我们一般不单独使用"坏字符"规则。
"好后缀"
在上图中,bc 便是"好后缀"(其本质是:已核对的子字符串),好后缀有什么作用呢?
如果有正确答案,答案必然包含"好后缀",不包含"好后缀"的必然不是答案,因此我们可以通过寻找"好后缀"的左侧找"一致项"来排除错误选项,"一致项"可以与"好后缀"部分重复,但不能"完全重复"

如图,我们通过"好后缀"规则滑动了3位。
为什么要滑动3位?模式串中的"一致项"在"好后缀前三位",因此"对比项"后移了三位,使得bc对上了"一致项"
找不到"好后缀"时,可能会产生问题"过度滑动"

因此,"好后缀"还有第二规则,好后缀的子后缀如果在模式串中存在且为前缀,也可以进行滑动。
为什么必须是前缀呢?因为如果不是前缀,"一致项"就必须是完整的,但现在找不到完整的,只有前缀允许不完整,实际上就是想法设法利用了部分"好后缀"
两个规则的配合
上文提到了单独使用"坏字符"会产生"倒退"现象,好在我们有两个规则:"坏字符"和"好后缀,将两个规则取到的滑动值中取大值,便实现了这个算法。
KMP
待补充。。。