真没想到字符串匹配算法还挺难的。。。

写在前面:

最近在看王争老师的《数据结构与算法之美》,看到了字符串匹配算法,发现里面门道挺多,光是想搞明白点就花了几天功夫,鉴于我付出的精力,必须好好记录一下。

因为目前只看了单模式匹配,所以只说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

待补充。。。

相关推荐
浅念-1 小时前
刷穿LeetCode:BFS 解决 Flood Fill 算法
数据结构·c++·算法·leetcode·职场和发展·bfs·宽度优先
做cv的小昊2 小时前
【TJU】研究生应用统计学课程笔记(8)——第四章 线性模型(4.1 一元线性回归分析)
笔记·线性代数·算法·数学建模·回归·线性回归·概率论
贾斯汀玛尔斯2 小时前
每天学一个算法--倒排索引(Inverted Index)
算法·inverted-index
小e说说2 小时前
打破偏科困境:这些学习软件助孩子重燃学习热情
算法
月昤昽3 小时前
autoCAD二次开发 4.正多边形与collection区分
算法·c#·二次开发·autocad二次开发
休息一下接着来3 小时前
C++ 固定容量环形队列实现
c++·算法
im_AMBER4 小时前
手撕hot100之矩阵!看完这篇就AC~
javascript·数据结构·线性代数·算法·leetcode·矩阵
笨笨饿4 小时前
#79_NOP()嵌入式C语言中内联汇编宏的抽象封装模式研究
linux·c语言·网络·驱动开发·算法·硬件工程·个人开发
风萧萧19995 小时前
问答样例如何在RAG问答中使用?
算法
七夜zippoe5 小时前
DolphinDB分区策略:HASH分区与COMPO分区
算法·哈希算法·hash·dolphindb·compo